From 9dcdc6c566f41faba969a03dc7cbb96bb40f3d75 Mon Sep 17 00:00:00 2001 From: jerome delattre Date: Thu, 7 Jun 2018 18:49:50 -0400 Subject: [PATCH] #ROBOMERGE-AUTHOR: jerome.delattre Copying //Tasks/UE4/Release-4.20-EnterpriseLateFeatures to Release-4.20 (//UE4/Release-4.20) #rb simon.tourangeau #jira UE-59798, UE-58919, UE-59480 #ROBOMERGE-SOURCE: CL 4119095 in //UE4/Release-4.20/... #ROBOMERGE-BOT: RELEASE (Release-4.20 -> Release-Staging-4.20) [CL 4119100 by jerome delattre in Staging-4.20 branch] --- Engine/Build/Commit.gitdeps.xml | 183 ++- .../OpenCVLensDistortion.uplugin | 50 + .../Private/DisplacementMapGeneration.usf | 97 ++ .../Source/OpenCVHelper/OpenCVHelper.Build.cs | 0 .../Private/OpenCVHelperModule.cpp | 2 +- .../OpenCVHelper/Public/IOpenCVHelperModule.h | 0 .../Source/OpenCVHelper/Public/OpenCVHelper.h | 0 .../OpenCVLensCalibration.Build.cs} | 14 +- .../Private/OpenCVLensCalibrationModule.cpp | 19 + .../Private/OpenCVLensCalibrator.cpp | 287 ++++ .../Private/OpenCVLensCalibrator.h | 119 ++ .../Public/IOpenCVLensCalibrationModule.h | 45 + .../OpenCVLensDistortion.Build.cs | 45 + ...LensDistortionDisplacementMapRendering.cpp | 194 +++ .../OpenCVLensDistortionBlueprintLibrary.cpp | 23 + .../Private/OpenCVLensDistortionModule.cpp | 21 + .../OpenCVLensDistortionParameters.cpp | 145 ++ .../Public/IOpenCVLensDistortionModule.h | 44 + .../OpenCVLensDistortionBlueprintLibrary.h | 62 + .../Public/OpenCVLensDistortionParameters.h | 179 +++ .../Source/ThirdParty/OpenCV/OpenCV.Build.cs | 0 .../Source/ThirdParty/OpenCV/OpenCV.tps | 0 .../Source/ThirdParty/OpenCV/OpenEXR.tps | 0 .../Source/ThirdParty/OpenCV/build.bat | 0 .../ThirdParty/OpenCV/cmake_options.txt | 0 .../ThirdParty/OpenCV/include/opencv/cv.h | 0 .../ThirdParty/OpenCV/include/opencv/cv.hpp | 0 .../ThirdParty/OpenCV/include/opencv/cvaux.h | 0 .../OpenCV/include/opencv/cvaux.hpp | 0 .../OpenCV/include/opencv/cvwimage.h | 0 .../ThirdParty/OpenCV/include/opencv/cxcore.h | 0 .../OpenCV/include/opencv/cxcore.hpp | 0 .../OpenCV/include/opencv/cxeigen.hpp | 0 .../ThirdParty/OpenCV/include/opencv/cxmisc.h | 0 .../OpenCV/include/opencv/highgui.h | 0 .../ThirdParty/OpenCV/include/opencv/ml.h | 0 .../OpenCV/include/opencv2/calib3d.hpp | 0 .../include/opencv2/calib3d/calib3d.hpp | 0 .../include/opencv2/calib3d/calib3d_c.h | 0 .../OpenCV/include/opencv2/core.hpp | 0 .../OpenCV/include/opencv2/core/affine.hpp | 0 .../OpenCV/include/opencv2/core/base.hpp | 0 .../include/opencv2/core/bufferpool.hpp | 0 .../OpenCV/include/opencv2/core/core.hpp | 0 .../OpenCV/include/opencv2/core/core_c.h | 0 .../OpenCV/include/opencv2/core/cuda.hpp | 0 .../OpenCV/include/opencv2/core/cuda.inl.hpp | 0 .../opencv2/core/cuda_stream_accessor.hpp | 0 .../include/opencv2/core/cuda_types.hpp | 0 .../include/opencv2/core/cv_cpu_dispatch.h | 0 .../include/opencv2/core/cv_cpu_helper.h | 0 .../OpenCV/include/opencv2/core/cvdef.h | 0 .../OpenCV/include/opencv2/core/cvstd.hpp | 0 .../OpenCV/include/opencv2/core/cvstd.inl.hpp | 0 .../OpenCV/include/opencv2/core/directx.hpp | 0 .../OpenCV/include/opencv2/core/eigen.hpp | 0 .../OpenCV/include/opencv2/core/fast_math.hpp | 0 .../OpenCV/include/opencv2/core/hal/hal.hpp | 0 .../include/opencv2/core/hal/interface.h | 0 .../include/opencv2/core/hal/intrin.hpp | 0 .../include/opencv2/core/hal/intrin_cpp.hpp | 0 .../include/opencv2/core/hal/intrin_neon.hpp | 0 .../include/opencv2/core/hal/intrin_sse.hpp | 0 .../include/opencv2/core/hal/intrin_vsx.hpp | 0 .../OpenCV/include/opencv2/core/ippasync.hpp | 0 .../OpenCV/include/opencv2/core/mat.hpp | 0 .../OpenCV/include/opencv2/core/mat.inl.hpp | 0 .../OpenCV/include/opencv2/core/matx.hpp | 0 .../include/opencv2/core/neon_utils.hpp | 0 .../OpenCV/include/opencv2/core/ocl.hpp | 0 .../include/opencv2/core/ocl_genbase.hpp | 0 .../OpenCV/include/opencv2/core/opengl.hpp | 0 .../include/opencv2/core/operations.hpp | 0 .../OpenCV/include/opencv2/core/optim.hpp | 0 .../OpenCV/include/opencv2/core/ovx.hpp | 0 .../include/opencv2/core/persistence.hpp | 0 .../OpenCV/include/opencv2/core/ptr.inl.hpp | 0 .../OpenCV/include/opencv2/core/saturate.hpp | 0 .../OpenCV/include/opencv2/core/softfloat.hpp | 0 .../OpenCV/include/opencv2/core/sse_utils.hpp | 0 .../OpenCV/include/opencv2/core/traits.hpp | 0 .../OpenCV/include/opencv2/core/types.hpp | 0 .../OpenCV/include/opencv2/core/types_c.h | 0 .../OpenCV/include/opencv2/core/utility.hpp | 0 .../include/opencv2/core/utils/logger.hpp | 0 .../include/opencv2/core/utils/trace.hpp | 0 .../OpenCV/include/opencv2/core/va_intel.hpp | 0 .../OpenCV/include/opencv2/core/version.hpp | 0 .../OpenCV/include/opencv2/core/vsx_utils.hpp | 0 .../OpenCV/include/opencv2/core/wimage.hpp | 0 .../OpenCV/include/opencv2/cvconfig.h | 0 .../ThirdParty/OpenCV/include/opencv2/dnn.hpp | 0 .../OpenCV/include/opencv2/dnn/all_layers.hpp | 0 .../OpenCV/include/opencv2/dnn/dict.hpp | 0 .../OpenCV/include/opencv2/dnn/dnn.hpp | 0 .../OpenCV/include/opencv2/dnn/dnn.inl.hpp | 0 .../include/opencv2/dnn/layer.details.hpp | 0 .../OpenCV/include/opencv2/dnn/layer.hpp | 0 .../include/opencv2/dnn/shape_utils.hpp | 0 .../OpenCV/include/opencv2/features2d.hpp | 0 .../include/opencv2/features2d/features2d.hpp | 0 .../OpenCV/include/opencv2/flann.hpp | 0 .../include/opencv2/flann/all_indices.h | 0 .../OpenCV/include/opencv2/flann/allocator.h | 0 .../OpenCV/include/opencv2/flann/any.h | 0 .../include/opencv2/flann/autotuned_index.h | 0 .../include/opencv2/flann/composite_index.h | 0 .../OpenCV/include/opencv2/flann/config.h | 0 .../OpenCV/include/opencv2/flann/defines.h | 0 .../OpenCV/include/opencv2/flann/dist.h | 0 .../OpenCV/include/opencv2/flann/dummy.h | 0 .../include/opencv2/flann/dynamic_bitset.h | 0 .../OpenCV/include/opencv2/flann/flann.hpp | 0 .../include/opencv2/flann/flann_base.hpp | 0 .../OpenCV/include/opencv2/flann/general.h | 0 .../include/opencv2/flann/ground_truth.h | 0 .../OpenCV/include/opencv2/flann/hdf5.h | 0 .../OpenCV/include/opencv2/flann/heap.h | 0 .../flann/hierarchical_clustering_index.h | 0 .../include/opencv2/flann/index_testing.h | 0 .../include/opencv2/flann/kdtree_index.h | 0 .../opencv2/flann/kdtree_single_index.h | 0 .../include/opencv2/flann/kmeans_index.h | 0 .../include/opencv2/flann/linear_index.h | 0 .../OpenCV/include/opencv2/flann/logger.h | 0 .../OpenCV/include/opencv2/flann/lsh_index.h | 0 .../OpenCV/include/opencv2/flann/lsh_table.h | 0 .../OpenCV/include/opencv2/flann/matrix.h | 0 .../include/opencv2/flann/miniflann.hpp | 0 .../OpenCV/include/opencv2/flann/nn_index.h | 0 .../include/opencv2/flann/object_factory.h | 0 .../OpenCV/include/opencv2/flann/params.h | 0 .../OpenCV/include/opencv2/flann/random.h | 0 .../OpenCV/include/opencv2/flann/result_set.h | 0 .../OpenCV/include/opencv2/flann/sampling.h | 0 .../OpenCV/include/opencv2/flann/saving.h | 0 .../include/opencv2/flann/simplex_downhill.h | 0 .../OpenCV/include/opencv2/flann/timer.h | 0 .../OpenCV/include/opencv2/highgui.hpp | 0 .../include/opencv2/highgui/highgui.hpp | 0 .../include/opencv2/highgui/highgui_c.h | 0 .../OpenCV/include/opencv2/imgcodecs.hpp | 0 .../include/opencv2/imgcodecs/imgcodecs.hpp | 0 .../include/opencv2/imgcodecs/imgcodecs_c.h | 0 .../OpenCV/include/opencv2/imgcodecs/ios.h | 0 .../OpenCV/include/opencv2/imgproc.hpp | 0 .../imgproc/detail/distortion_model.hpp | 0 .../include/opencv2/imgproc/hal/hal.hpp | 0 .../include/opencv2/imgproc/hal/interface.h | 0 .../include/opencv2/imgproc/imgproc.hpp | 0 .../include/opencv2/imgproc/imgproc_c.h | 0 .../OpenCV/include/opencv2/imgproc/types_c.h | 0 .../ThirdParty/OpenCV/include/opencv2/ml.hpp | 0 .../OpenCV/include/opencv2/ml/ml.hpp | 0 .../OpenCV/include/opencv2/objdetect.hpp | 0 .../objdetect/detection_based_tracker.hpp | 0 .../include/opencv2/objdetect/objdetect.hpp | 0 .../include/opencv2/objdetect/objdetect_c.h | 0 .../OpenCV/include/opencv2/opencv.hpp | 0 .../OpenCV/include/opencv2/opencv_modules.hpp | 0 .../OpenCV/include/opencv2/photo.hpp | 0 .../OpenCV/include/opencv2/photo/cuda.hpp | 0 .../OpenCV/include/opencv2/photo/photo.hpp | 0 .../OpenCV/include/opencv2/photo/photo_c.h | 0 .../OpenCV/include/opencv2/shape.hpp | 0 .../OpenCV/include/opencv2/shape/emdL1.hpp | 0 .../include/opencv2/shape/hist_cost.hpp | 0 .../OpenCV/include/opencv2/shape/shape.hpp | 0 .../include/opencv2/shape/shape_distance.hpp | 0 .../opencv2/shape/shape_transformer.hpp | 0 .../OpenCV/include/opencv2/stitching.hpp | 0 .../opencv2/stitching/detail/autocalib.hpp | 0 .../opencv2/stitching/detail/blenders.hpp | 0 .../opencv2/stitching/detail/camera.hpp | 0 .../stitching/detail/exposure_compensate.hpp | 0 .../opencv2/stitching/detail/matchers.hpp | 0 .../stitching/detail/motion_estimators.hpp | 0 .../opencv2/stitching/detail/seam_finders.hpp | 0 .../opencv2/stitching/detail/timelapsers.hpp | 0 .../include/opencv2/stitching/detail/util.hpp | 0 .../opencv2/stitching/detail/util_inl.hpp | 0 .../opencv2/stitching/detail/warpers.hpp | 0 .../opencv2/stitching/detail/warpers_inl.hpp | 0 .../include/opencv2/stitching/warpers.hpp | 0 .../OpenCV/include/opencv2/superres.hpp | 0 .../include/opencv2/superres/optical_flow.hpp | 0 .../OpenCV/include/opencv2/video.hpp | 0 .../include/opencv2/video/background_segm.hpp | 0 .../OpenCV/include/opencv2/video/tracking.hpp | 0 .../OpenCV/include/opencv2/video/tracking_c.h | 0 .../OpenCV/include/opencv2/video/video.hpp | 0 .../OpenCV/include/opencv2/videoio.hpp | 0 .../OpenCV/include/opencv2/videoio/cap_ios.h | 0 .../include/opencv2/videoio/videoio.hpp | 0 .../include/opencv2/videoio/videoio_c.h | 0 .../OpenCV/include/opencv2/videostab.hpp | 0 .../include/opencv2/videostab/deblurring.hpp | 0 .../opencv2/videostab/fast_marching.hpp | 0 .../opencv2/videostab/fast_marching_inl.hpp | 0 .../opencv2/videostab/frame_source.hpp | 0 .../opencv2/videostab/global_motion.hpp | 0 .../include/opencv2/videostab/inpainting.hpp | 0 .../OpenCV/include/opencv2/videostab/log.hpp | 0 .../include/opencv2/videostab/motion_core.hpp | 0 .../opencv2/videostab/motion_stabilizing.hpp | 0 .../opencv2/videostab/optical_flow.hpp | 0 .../opencv2/videostab/outlier_rejection.hpp | 0 .../include/opencv2/videostab/ring_buffer.hpp | 0 .../include/opencv2/videostab/stabilizer.hpp | 0 .../opencv2/videostab/wobble_suppression.hpp | 0 .../OpenCV/include/opencv2/world.hpp | 0 .../Source/ThirdParty/OpenCV/libPNG.tps | 0 .../Source/ThirdParty/OpenCV/libTiff.tps | 0 .../Source/ThirdParty/OpenCV/zlib.tps | 0 .../Private/EditorScriptingUtils.cpp | 36 +- .../Source/MeshEditor/MeshEditorCommands.cpp | 17 +- .../Source/MeshEditor/MeshEditorMode.cpp | 440 +++--- .../Source/MeshEditor/MeshEditorMode.h | 69 +- .../MeshEditor/MeshEditorModeToolkit.cpp | 68 +- .../MeshEditorSelectionModifiers.cpp | 187 +++ .../Source/MeshEditor/MeshEditorStyle.cpp | 35 + .../Source/MeshEditor/MeshEditorUtilities.cpp | 105 ++ .../MeshElementViewportTransformable.cpp | 2 +- .../Source/MeshEditor/OverlayComponent.cpp | 5 +- .../Public/IMeshEditorModeEditingContract.h | 2 +- .../Public/IMeshEditorModeUIContract.h | 4 + .../{ => Public}/MeshEditorAssetContainer.h | 2 +- .../MeshEditor/Public/MeshEditorCommands.h | 25 +- .../Public/MeshEditorSelectionModifiers.h | 118 ++ .../MeshEditorStaticMeshAdapter.h | 7 +- .../MeshEditor/{ => Public}/MeshEditorStyle.h | 2 +- .../MeshEditor/Public/MeshEditorUtilities.h | 25 + .../Source/MeshEditor/Public/MeshElement.h | 29 +- .../{ => Public}/OverlayComponent.h | 2 +- .../{ => Public}/WireframeMeshComponent.h | 4 +- .../Source/PolygonModeling/AssignMaterial.cpp | 58 + .../PolygonModeling/DeleteMeshElement.cpp | 133 ++ .../Source/PolygonModeling/FlipPolygon.cpp | 63 + .../PolygonModeling/Public/AssignMaterial.h | 23 + .../{ => Public}/BevelOrInsetPolygon.h | 2 +- .../Public/DeleteMeshElement.h | 23 + .../{ => Public}/EditVertexOrEdgeSharpness.h | 2 +- .../PolygonModeling/{ => Public}/ExtendEdge.h | 2 +- .../{ => Public}/ExtendVertex.h | 2 +- .../{ => Public}/ExtrudePolygon.h | 2 +- .../PolygonModeling/Public/FlipPolygon.h | 21 + .../{ => Public}/HardenOrSoftenEdge.h | 2 +- .../{ => Public}/InsertEdgeLoop.h | 2 +- .../{ => Public}/QuadrangulateMesh.h | 2 +- .../PolygonModeling/{ => Public}/RemoveEdge.h | 2 +- .../{ => Public}/RemoveVertex.h | 2 +- .../PolygonModeling/{ => Public}/SplitEdge.h | 2 +- .../{ => Public}/SplitPolygon.h | 2 +- .../{ => Public}/TessellatePolygon.h | 2 +- .../DatasmithContent/DatasmithContent.uplugin | 6 +- .../Private/DatasmithImportOptions.cpp | 1 + .../DatasmithStaticMeshTemplate.cpp | 6 +- .../Public/DatasmithImportOptions.h | 3 + .../PythonScriptPlugin.uplugin | 4 +- .../Experimental/Shotgun/Shotgun.uplugin | 4 + .../Source/Shotgun/Private/ShotgunEngine.cpp | 3 +- .../Public/MagicLeapIdentityTypes.h | 2 +- .../Plugins/Media/AjaMedia/AjaMedia.uplugin | 2 +- .../Source/AjaMedia/Private/Aja/Aja.cpp | 53 - .../Source/AjaMedia/Private/Aja/Aja.h | 1 - .../AjaMedia/Private/AjaMediaModule.cpp | 34 +- .../Source/AjaMedia/Private/AjaMediaPrivate.h | 13 +- .../Private/Assets/AjaCustomTimeStep.cpp | 6 +- .../Private/Assets/AjaMediaFinder.cpp | 126 +- .../Private/Assets/AjaMediaOutput.cpp | 62 +- .../Private/Assets/AjaMediaSource.cpp | 29 +- .../Private/Assets/AjaTimecodeProvider.cpp | 7 +- .../Private/Player/AjaMediaPlayer.cpp | 74 +- .../AjaMedia/Private/Player/AjaMediaPlayer.h | 7 +- .../AjaMedia/Public/AjaCustomTimeStep.h | 6 +- .../Source/AjaMedia/Public/AjaMediaFinder.h | 78 +- .../Source/AjaMedia/Public/AjaMediaOutput.h | 43 +- .../Source/AjaMedia/Public/AjaMediaSource.h | 18 +- .../AjaMedia/Public/AjaTimecodeProvider.h | 12 +- .../AjaMediaEditor/AjaMediaEditor.Build.cs | 2 + .../Private/AjaMediaEditorModule.cpp | 50 +- .../AjaMediaModeCustomization.cpp | 78 +- .../AjaMediaModeCustomization.h | 14 +- .../AjaMediaPortCustomization.cpp | 30 +- .../AjaMediaOutput/AjaMediaOutput.Build.cs | 3 +- .../Private/AjaMediaViewportOutput.cpp | 2 +- .../Private/AjaMediaViewportOutputImpl.cpp | 112 +- .../Private/AjaMediaViewportOutputImpl.h | 3 - .../Public/AudioCaptureTimecodeProvider.h | 2 +- .../MediaFrameworkUtilities.uplugin | 29 + .../MediaFrameworkUtilities.Build.cs | 21 + .../Private/MediaBundle.cpp | 62 + .../Private/MediaBundleActorBase.cpp | 189 +++ .../Private/MediaFrameworkUtilitiesModule.cpp | 15 + .../Public/MediaBundle.h | 93 ++ .../Public/MediaBundleActorBase.h | 86 ++ .../MediaFrameworkUtilitiesEditor.Build.cs | 29 + .../Private/MediaBundleActorDetails.cpp | 107 ++ .../Private/MediaBundleActorDetails.h | 18 + .../Private/MediaBundleFactoryNew.cpp | 134 ++ .../Private/MediaBundleFactoryNew.h | 55 + .../MediaFrameworkUtilitiesEditorModule.cpp | 57 + .../MediaFrameworkUtilitiesPlacement.cpp | 224 ++++ .../MediaFrameworkUtilitiesPlacement.h | 12 + .../Private/MediaPlayerEditorModule.cpp | 24 +- .../Private/MediaPlayerInputSource.cpp | 35 +- .../Private/TimecodeSynchronizer.cpp | 479 +++++-- .../Public/MediaPlayerInputSource.h | 3 - .../Public/TimecodeSynchronizer.h | 83 +- .../TimecodeSynchronizerEditorToolkit.cpp | 235 +++- .../TimecodeSynchronizerEditorToolkit.h | 11 +- .../UI/TimecodeSynchronizerEditorCommand.cpp | 2 - .../UI/TimecodeSynchronizerEditorCommand.h | 1 - .../UI/TimecodeSynchronizerEditorStyle.cpp | 6 +- .../STimecodeSynchronizerSourceViewport.cpp | 26 +- .../EditableStaticMeshAdapter.cpp | 7 +- .../MixedRealityCaptureFramework.uplugin | 24 +- .../Private/MrcCalibrationModule.cpp | 29 - .../Private/MrcOpenCVCalibrator.cpp | 207 --- .../Private/MrcOpenCVCalibrator.h | 78 -- .../Public/IMrcCalibrationModule.h | 20 - .../MixedRealityCaptureFramework.Build.cs | 3 +- .../Private/MixedRealityCaptureComponent.cpp | 20 +- .../MrcGarbageMatteCaptureComponent.cpp | 34 +- .../Private/MrcLensDistortion.cpp | 141 -- .../Public/MixedRealityCaptureComponent.h | 16 +- .../Public/MrcCalibrationData.h | 7 +- .../Public/MrcGarbageMatteCaptureComponent.h | 10 +- .../Public/MrcLensDistortion.h | 120 -- .../DisplayCluster/DisplayCluster.Build.cs | 102 ++ .../DisplayClusterBlueprintAPIImpl.cpp | 481 +++++++ .../DisplayClusterBlueprintAPIImpl.h | 185 +++ .../Blueprints/DisplayClusterBlueprintLib.cpp | 17 + .../DisplayClusterClusterNodeCtrlBase.cpp | 65 + .../DisplayClusterClusterNodeCtrlBase.h | 32 + .../DisplayClusterClusterNodeCtrlMaster.cpp | 148 ++ .../DisplayClusterClusterNodeCtrlMaster.h | 54 + .../DisplayClusterClusterNodeCtrlSlave.cpp | 180 +++ .../DisplayClusterClusterNodeCtrlSlave.h | 65 + .../Controller/DisplayClusterNodeCtrlBase.cpp | 56 + .../Controller/DisplayClusterNodeCtrlBase.h | 71 + .../DisplayClusterNodeCtrlStandalone.cpp | 75 ++ .../DisplayClusterNodeCtrlStandalone.h | 53 + .../IPDisplayClusterNodeController.h | 29 + .../Cluster/DisplayClusterClusterManager.cpp | 448 +++++++ .../Cluster/DisplayClusterClusterManager.h | 102 ++ .../Cluster/IPDisplayClusterClusterManager.h | 37 + .../Checker/DisplayClusterConfigChecker.cpp | 72 + .../Checker/DisplayClusterConfigChecker.h | 33 + .../Config/DisplayClusterConfigManager.cpp | 460 +++++++ .../Config/DisplayClusterConfigManager.h | 155 +++ .../Config/DisplayClusterConfigTypes.cpp | 299 +++++ .../Config/IPDisplayClusterConfigManager.h | 24 + .../Parser/DisplayClusterConfigParser.cpp | 80 ++ .../Parser/DisplayClusterConfigParser.h | 44 + .../DisplayClusterConfigParserDebugAuto.cpp | 64 + .../DisplayClusterConfigParserDebugAuto.h | 21 + .../Parser/DisplayClusterConfigParserText.cpp | 98 ++ .../Parser/DisplayClusterConfigParserText.h | 37 + .../Parser/DisplayClusterConfigParserXml.cpp | 13 + .../Parser/DisplayClusterConfigParserXml.h | 28 + .../IDisplayClusterConfigParserListener.h | 28 + .../Private/DisplayClusterBuildConfig.h | 13 + .../Private/DisplayClusterConstants.h | 30 + .../Private/DisplayClusterGlobals.cpp | 7 + .../Private/DisplayClusterGlobals.h | 9 + .../Private/DisplayClusterModule.cpp | 201 +++ .../Private/DisplayClusterModule.h | 86 ++ .../Private/DisplayClusterStrings.h | 193 +++ .../Basics/DisplayClusterGameEngine.cpp | 227 ++++ .../Classes/Basics/DisplayClusterGameMode.cpp | 212 +++ .../Basics/DisplayClusterGameModeDefault.cpp | 26 + .../Game/Classes/Basics/DisplayClusterHUD.cpp | 21 + .../Basics/DisplayClusterPlayerController.cpp | 33 + .../Scene/DisplayClusterCameraComponent.cpp | 36 + .../Game/Classes/Scene/DisplayClusterPawn.cpp | 108 ++ .../Scene/DisplayClusterPawnDefault.cpp | 248 ++++ .../Scene/DisplayClusterSceneComponent.cpp | 84 ++ .../DisplayClusterSceneComponentSync.cpp | 93 ++ ...DisplayClusterSceneComponentSyncParent.cpp | 70 + .../DisplayClusterSceneComponentSyncThis.cpp | 67 + .../Scene/DisplayClusterScreenComponent.cpp | 88 ++ .../Classes/Scene/DisplayClusterSettings.cpp | 20 + .../Game/DisplayClusterGameManager.cpp | 491 +++++++ .../Private/Game/DisplayClusterGameManager.h | 127 ++ .../Game/IPDisplayClusterGameManager.h | 29 + .../DisplayCluster/Private/IPDisplayCluster.h | 35 + .../Private/IPDisplayClusterManager.h | 48 + .../Devices/DisplayClusterInputDeviceBase.h | 83 ++ .../Devices/DisplayClusterInputDeviceTraits.h | 52 + .../Devices/IDisplayClusterInputDevice.h | 32 + .../DisplayClusterVrpnAnalogInputData.h | 13 + ...isplayClusterVrpnAnalogInputDataHolder.cpp | 61 + .../DisplayClusterVrpnAnalogInputDataHolder.h | 39 + .../DisplayClusterVrpnAnalogInputDevice.cpp | 75 ++ .../DisplayClusterVrpnAnalogInputDevice.h | 38 + .../DisplayClusterVrpnButtonInputData.h | 15 + ...isplayClusterVrpnButtonInputDataHolder.cpp | 62 + .../DisplayClusterVrpnButtonInputDataHolder.h | 38 + .../DisplayClusterVrpnButtonInputDevice.cpp | 91 ++ .../DisplayClusterVrpnButtonInputDevice.h | 39 + .../DisplayClusterVrpnTrackerInputData.h | 15 + ...splayClusterVrpnTrackerInputDataHolder.cpp | 93 ++ ...DisplayClusterVrpnTrackerInputDataHolder.h | 38 + .../DisplayClusterVrpnTrackerInputDevice.cpp | 252 ++++ .../DisplayClusterVrpnTrackerInputDevice.h | 68 + .../Input/DisplayClusterInputManager.cpp | 488 +++++++ .../Input/DisplayClusterInputManager.h | 119 ++ .../Input/IPDisplayClusterInputManager.h | 25 + .../Private/Misc/DisplayClusterAppExit.cpp | 97 ++ .../Private/Misc/DisplayClusterAppExit.h | 32 + .../Private/Misc/DisplayClusterBarrier.cpp | 112 ++ .../Private/Misc/DisplayClusterBarrier.h | 59 + .../Private/Misc/DisplayClusterHelpers.h | 206 +++ .../Private/Misc/DisplayClusterLog.cpp | 17 + .../Private/Misc/DisplayClusterLog.h | 47 + .../Misc/DisplayClusterTypesConverter.h | 62 + .../Private/Network/DisplayClusterClient.cpp | 115 ++ .../Private/Network/DisplayClusterClient.h | 47 + .../Private/Network/DisplayClusterMessage.cpp | 94 ++ .../Private/Network/DisplayClusterMessage.h | 84 ++ .../Private/Network/DisplayClusterServer.cpp | 106 ++ .../Private/Network/DisplayClusterServer.h | 79 ++ .../Private/Network/DisplayClusterSession.cpp | 68 + .../Private/Network/DisplayClusterSession.h | 35 + .../Network/DisplayClusterSocketOps.cpp | 194 +++ .../Private/Network/DisplayClusterSocketOps.h | 58 + .../Network/DisplayClusterTcpListener.cpp | 153 +++ .../Network/DisplayClusterTcpListener.h | 67 + .../Network/IDisplayClusterSessionListener.h | 27 + .../IPDisplayClusterClusterSyncProtocol.h | 35 + .../IPDisplayClusterSwapSyncProtocol.h | 15 + .../DisplayClusterClusterSyncClient.cpp | 104 ++ .../DisplayClusterClusterSyncClient.h | 33 + .../DisplayClusterClusterSyncMsg.h | 52 + .../DisplayClusterClusterSyncService.cpp | 191 +++ .../DisplayClusterClusterSyncService.h | 58 + .../Network/Service/DisplayClusterService.cpp | 50 + .../Network/Service/DisplayClusterService.h | 34 + .../SwapSync/DisplayClusterSwapSyncClient.cpp | 47 + .../SwapSync/DisplayClusterSwapSyncClient.h | 26 + .../SwapSync/DisplayClusterSwapSyncMsg.h | 23 + .../DisplayClusterSwapSyncService.cpp | 107 ++ .../SwapSync/DisplayClusterSwapSyncService.h | 46 + .../Debug/DisplayClusterDeviceDebug.cpp | 28 + .../Devices/Debug/DisplayClusterDeviceDebug.h | 21 + .../Devices/DisplayClusterDeviceBase.cpp | 430 ++++++ .../Render/Devices/DisplayClusterDeviceBase.h | 208 +++ .../Devices/DisplayClusterDeviceInternals.cpp | 95 ++ .../Devices/DisplayClusterDeviceInternals.h | 103 ++ .../Devices/DisplayClusterViewportArea.h | 52 + .../DisplayClusterDeviceMonoscopicD3D11.cpp | 38 + .../DisplayClusterDeviceMonoscopicD3D11.h | 21 + .../DisplayClusterDeviceMonoscopicD3D12.cpp | 38 + .../DisplayClusterDeviceMonoscopicD3D12.h | 24 + .../DisplayClusterDeviceMonoscopicOpenGL.cpp | 62 + .../DisplayClusterDeviceMonoscopicOpenGL.h | 20 + ...splayClusterDeviceQuadBufferStereoBase.cpp | 93 ++ ...DisplayClusterDeviceQuadBufferStereoBase.h | 29 + ...playClusterDeviceQuadBufferStereoD3D11.cpp | 105 ++ ...isplayClusterDeviceQuadBufferStereoD3D11.h | 30 + ...playClusterDeviceQuadBufferStereoD3D12.cpp | 103 ++ ...isplayClusterDeviceQuadBufferStereoD3D12.h | 33 + ...layClusterDeviceQuadBufferStereoOpenGL.cpp | 363 +++++ ...splayClusterDeviceQuadBufferStereoOpenGL.h | 37 + .../DisplayClusterDeviceSideBySide.cpp | 32 + .../DisplayClusterDeviceSideBySide.h | 26 + .../DisplayClusterDeviceTopBottom.cpp | 31 + .../TopBottom/DisplayClusterDeviceTopBottom.h | 25 + .../Render/DisplayClusterRenderManager.cpp | 254 ++++ .../Render/DisplayClusterRenderManager.h | 56 + .../Render/IPDisplayClusterRenderManager.h | 19 + .../Blueprints/DisplayClusterBlueprintLib.h | 24 + .../Blueprints/IDisplayClusterBlueprintAPI.h | 189 +++ .../Cluster/IDisplayClusterClusterManager.h | 22 + .../IDisplayClusterClusterSyncObject.h | 23 + .../Public/Config/DisplayClusterConfigTypes.h | 169 +++ .../Config/IDisplayClusterConfigManager.h | 55 + .../Public/DisplayClusterCameraComponent.h | 28 + .../Public/DisplayClusterGameEngine.h | 44 + .../Public/DisplayClusterGameMode.h | 55 + .../Public/DisplayClusterGameModeDefault.h | 21 + .../DisplayCluster/Public/DisplayClusterHUD.h | 27 + .../Public/DisplayClusterOperationMode.h | 15 + .../Public/DisplayClusterPawn.h | 74 + .../Public/DisplayClusterPawnDefault.h | 103 ++ .../Public/DisplayClusterPlayerController.h | 21 + .../Public/DisplayClusterSceneComponent.h | 43 + .../Public/DisplayClusterSceneComponentSync.h | 72 + .../DisplayClusterSceneComponentSyncParent.h | 40 + .../DisplayClusterSceneComponentSyncThis.h | 40 + .../Public/DisplayClusterScreenComponent.h | 38 + .../Public/DisplayClusterSettings.h | 51 + .../Public/Game/IDisplayClusterGameManager.h | 42 + .../DisplayCluster/Public/IDisplayCluster.h | 81 ++ .../Public/IDisplayClusterSerializable.h | 19 + .../IDisplayClusterStringSerializable.h | 17 + .../Input/IDisplayClusterInputManager.h | 42 + .../Render/IDisplayClusterRenderManager.h | 17 + .../Render/IDisplayClusterStereoDevice.h | 128 ++ .../DisplayClusterEditor.Build.cs | 26 + .../Private/DisplayClusterEditor.cpp | 49 + .../Private/DisplayClusterEditorEngine.cpp | 47 + .../Private/DisplayClusterEditorEngine.h | 29 + .../Private/DisplayClusterEditorLog.cpp | 7 + .../Private/DisplayClusterEditorLog.h | 9 + .../Private/DisplayClusterEditorSettings.cpp | 41 + .../Public/DisplayClusterEditor.h | 22 + .../Public/DisplayClusterEditorSettings.h | 29 + .../ThirdParty/Vrpn/Include/vrpn/quat.h | 546 ++++++++ .../Vrpn/Include/vrpn/vrpn_Analog.h | 210 +++ .../Vrpn/Include/vrpn/vrpn_Analog_Output.h | 193 +++ .../Vrpn/Include/vrpn/vrpn_Assert.h | 203 +++ .../Vrpn/Include/vrpn/vrpn_Auxiliary_Logger.h | 253 ++++ .../Vrpn/Include/vrpn/vrpn_BaseClass.h | 487 +++++++ .../Vrpn/Include/vrpn/vrpn_Button.h | 296 ++++ .../Vrpn/Include/vrpn/vrpn_Configure.h | 544 ++++++++ .../Vrpn/Include/vrpn/vrpn_Connection.h | 1185 +++++++++++++++++ .../ThirdParty/Vrpn/Include/vrpn/vrpn_Dial.h | 116 ++ .../Include/vrpn/vrpn_EndpointContainer.h | 364 +++++ .../Vrpn/Include/vrpn/vrpn_FileConnection.h | 326 +++++ .../Vrpn/Include/vrpn/vrpn_FileController.h | 47 + .../Vrpn/Include/vrpn/vrpn_ForceDevice.h | 730 ++++++++++ .../Vrpn/Include/vrpn/vrpn_Forwarder.h | 132 ++ .../Include/vrpn/vrpn_ForwarderController.h | 131 ++ .../Include/vrpn/vrpn_FunctionGenerator.h | 429 ++++++ .../Vrpn/Include/vrpn/vrpn_Imager.h | 804 +++++++++++ .../Vrpn/Include/vrpn/vrpn_LamportClock.h | 91 ++ .../ThirdParty/Vrpn/Include/vrpn/vrpn_Mutex.h | 333 +++++ .../ThirdParty/Vrpn/Include/vrpn/vrpn_Poser.h | 191 +++ .../Include/vrpn/vrpn_RedundantTransmission.h | 214 +++ .../Vrpn/Include/vrpn/vrpn_Serial.h | 91 ++ .../Vrpn/Include/vrpn/vrpn_SerialPort.h | 227 ++++ .../Vrpn/Include/vrpn/vrpn_Shared.h | 495 +++++++ .../Vrpn/Include/vrpn/vrpn_SharedObject.h | 572 ++++++++ .../ThirdParty/Vrpn/Include/vrpn/vrpn_Sound.h | 443 ++++++ .../ThirdParty/Vrpn/Include/vrpn/vrpn_Text.h | 102 ++ .../Vrpn/Include/vrpn/vrpn_Thread.h | 245 ++++ .../Vrpn/Include/vrpn/vrpn_Tracker.h | 518 +++++++ .../ThirdParty/Vrpn/Include/vrpn/vrpn_Types.h | 223 ++++ .../Vrpn/Include/vrpn/vrpn_WindowsH.h | 78 ++ .../Runtime/nDisplay/ThirdParty/Vrpn/VRPN.tps | 13 + .../Plugins/Runtime/nDisplay/nDisplay.uplugin | 39 + .../Private/LayoutUV.cpp | 38 +- .../Private/LayoutUV.h | 15 +- .../Private/MeshDescriptionOperations.cpp | 54 +- .../Private/QuadricMeshReduction.cpp | 193 +-- .../Private/AssetContextMenu.cpp | 54 +- .../ContentBrowser/Private/SMetaDataView.cpp | 148 ++ .../ContentBrowser/Private/SMetaDataView.h | 34 + .../Private/FrameRateCustomization.cpp | 28 +- .../Private/PluginWardenAuthorizer.cpp | 243 ++++ .../Private/PluginWardenAuthorizer.h | 79 ++ .../Private/PluginWardenModule.cpp | 143 +- .../PluginWarden/Private/PluginWardenModule.h | 11 +- .../Private/SAuthorizingPlugin.cpp | 285 +--- .../PluginWarden/Private/SAuthorizingPlugin.h | 61 +- .../SPropertyEditorEditInline.cpp | 13 + .../SPropertyEditorEditInline.h | 9 + .../ActorGroupDetailsCustomization.cpp | 10 +- .../Private/SSequenceRecorder.cpp | 41 +- .../Private/SSequenceRecorder.h | 1 + .../Private/SequenceRecorder.cpp | 42 +- .../Private/SequenceRecorder.h | 11 +- .../Private/SequenceRecorderActorGroup.cpp | 16 + .../Private/SequenceRecorderModule.cpp | 47 +- .../Public/ISequenceRecorder.h | 26 +- .../SequenceRecorderActorGroup.h | 22 +- .../Private/StaticMeshEditor.cpp | 65 +- .../Private/StaticMeshEditor.h | 8 +- .../Public/IStaticMeshEditor.h | 29 +- .../Programs/nDisplayLauncher/App.config | 6 + .../Source/Programs/nDisplayLauncher/App.xaml | 282 ++++ .../Programs/nDisplayLauncher/App.xaml.cs | 71 + .../nDisplayLauncher/AppLog/AppLogger.cs | 69 + .../nDisplayLauncher/Config/BaseInput.cs | 91 ++ .../nDisplayLauncher/Config/Camera.cs | 18 + .../nDisplayLauncher/Config/ClusterNode.cs | 198 +++ .../nDisplayLauncher/Config/ConfigItem.cs | 18 + .../nDisplayLauncher/Config/DynamicCamera.cs | 16 + .../nDisplayLauncher/Config/MasterNode.cs | 17 + .../nDisplayLauncher/Config/Parser.cs | 140 ++ .../nDisplayLauncher/Config/SceneNode.cs | 174 +++ .../nDisplayLauncher/Config/SceneNodeView.cs | 64 + .../nDisplayLauncher/Config/Screen.cs | 173 +++ .../nDisplayLauncher/Config/TrackerInput.cs | 164 +++ .../nDisplayLauncher/Config/VRConfig.cs | 801 +++++++++++ .../nDisplayLauncher/Config/Viewport.cs | 158 +++ .../Programs/nDisplayLauncher/MainWindow.xaml | 603 +++++++++ .../nDisplayLauncher/MainWindow.xaml.cs | 259 ++++ .../nDisplayLauncher/Other/ValidationRules.cs | 56 + .../Properties/AssemblyInfo.cs | 58 + .../Properties/Resources.Designer.cs | 65 + .../Properties/Resources.resx | 117 ++ .../Properties/Settings.Designer.cs | 27 + .../Properties/Settings.settings | 7 + .../nDisplayLauncher/Properties/app.manifest | 70 + .../Runner/Runner.DeployApp.cs | 106 ++ .../nDisplayLauncher/Runner/Runner.KillApp.cs | 32 + .../Runner/Runner.StartApp.cs | 91 ++ .../Runner/Runner.StartListeners.cs | 144 ++ .../Runner/Runner.StatusListeners.cs | 32 + .../Runner/Runner.StopListeners.cs | 36 + .../nDisplayLauncher/Runner/Runner.cs | 445 +++++++ .../Settings/RegistrySaver.cs | 171 +++ .../UIControl/AutoScroller.cs | 46 + .../UIControl/UnselectableListBox.cs | 30 + .../ValueConversion/SizeConverter.cs | 27 + .../nDisplayLauncher/nDisplayLauncher.csproj | 214 +++ .../Programs/nDisplayListener/App.config | 6 + .../Properties/AssemblyInfo.cs | 38 + .../nDisplayListener/Properties/app.manifest | 70 + .../nDisplayListener/nDisplayListener.cs | 296 ++++ .../nDisplayListener/nDisplayListener.csproj | 124 ++ .../Runtime/Core/Public/Misc/Timecode.h | 22 +- .../Runtime/Engine/Classes/Engine/Engine.h | 23 +- .../HierarchicalInstancedStaticMesh.cpp | 7 +- .../Source/Runtime/Engine/Private/Level.cpp | 28 +- .../Runtime/Engine/Private/StaticMesh.cpp | 2 +- .../Runtime/Engine/Private/UnrealEngine.cpp | 60 + .../Private/Assets/MediaPlayer.cpp | 3 +- .../Runtime/MediaAssets/Public/MediaPlayer.h | 8 +- .../Runtime/MediaAssets/Public/MediaSource.h | 2 +- .../Runtime/MediaIOCore/MediaIOCore.Build.cs | 17 +- .../Private/Player/MediaIOCorePlayerBase.cpp | 10 +- .../Private/Shared/MediaIOCoreEncodeTime.cpp | 91 ++ .../Public/MediaIOCoreEncodeTime.h | 43 + .../Public/MediaIOCorePlayerBase.h | 3 + .../Private/MeshDescription.cpp | 13 + .../MeshDescription/Public/MeshDescription.h | 2 +- .../Private/TimeSynchronizationSource.cpp | 10 + .../Public/TimeSynchronizationSource.h | 2 +- .../ThirdParty/AJA/Build/include/AJALib.h | 266 ++-- .../TP_nDisplay/Config/DefaultEditor.ini | 0 .../TP_nDisplay/Config/DefaultEngine.ini | 80 ++ Templates/TP_nDisplay/Config/DefaultGame.ini | 7 + Templates/TP_nDisplay/Config/DefaultInput.ini | 61 + Templates/TP_nDisplay/Config/TemplateDefs.ini | 28 + Templates/TP_nDisplay/TP_nDisplay.uproject | 15 + 639 files changed, 37864 insertions(+), 2583 deletions(-) create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/OpenCVLensDistortion.uplugin create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Shaders/Private/DisplacementMapGeneration.usf rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/OpenCVHelper/OpenCVHelper.Build.cs (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/OpenCVHelper/Private/OpenCVHelperModule.cpp (96%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/OpenCVHelper/Public/IOpenCVHelperModule.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/OpenCVHelper/Public/OpenCVHelper.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/MixedRealityCaptureCalibration.Build.cs => Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/OpenCVLensCalibration.Build.cs} (60%) create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrationModule.cpp create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrator.cpp create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrator.h create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Public/IOpenCVLensCalibrationModule.h create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/OpenCVLensDistortion.Build.cs create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/LensDistortionDisplacementMapRendering.cpp create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionBlueprintLibrary.cpp create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionModule.cpp create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionParameters.cpp create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/IOpenCVLensDistortionModule.h create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/OpenCVLensDistortionBlueprintLibrary.h create mode 100644 Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/OpenCVLensDistortionParameters.h rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/OpenCV.Build.cs (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/OpenCV.tps (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/OpenEXR.tps (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/build.bat (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/cmake_options.txt (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cv.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cv.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cvaux.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cvaux.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cvwimage.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cxcore.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cxcore.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cxeigen.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/cxmisc.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/highgui.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv/ml.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/calib3d.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/affine.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/base.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/bufferpool.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/core.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/core_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.inl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_stream_accessor.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_types.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_dispatch.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_helper.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cvdef.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.inl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/directx.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/eigen.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/fast_math.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/hal/hal.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/hal/interface.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_cpp.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_neon.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_sse.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_vsx.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/ippasync.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/mat.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/mat.inl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/matx.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/neon_utils.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/ocl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/ocl_genbase.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/opengl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/operations.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/optim.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/ovx.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/persistence.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/ptr.inl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/saturate.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/softfloat.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/sse_utils.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/traits.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/types.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/types_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/utility.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/utils/logger.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/utils/trace.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/va_intel.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/version.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/vsx_utils.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/core/wimage.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/cvconfig.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/dnn.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/dnn/all_layers.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/dnn/dict.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.inl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.details.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/dnn/shape_utils.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/features2d.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/features2d/features2d.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/all_indices.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/allocator.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/any.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/autotuned_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/composite_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/config.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/defines.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/dist.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/dummy.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/dynamic_bitset.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/flann.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/flann_base.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/general.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/ground_truth.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/hdf5.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/heap.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/hierarchical_clustering_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/index_testing.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_single_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/kmeans_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/linear_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/logger.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_table.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/matrix.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/miniflann.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/nn_index.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/object_factory.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/params.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/random.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/result_set.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/sampling.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/saving.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/simplex_downhill.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/flann/timer.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/highgui.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/ios.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgproc.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgproc/detail/distortion_model.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/hal.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/interface.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/imgproc/types_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/ml.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/ml/ml.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/objdetect.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/objdetect/detection_based_tracker.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/opencv.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/opencv_modules.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/photo.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/photo/cuda.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/photo/photo.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/photo/photo_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/shape.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/shape/emdL1.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/shape/hist_cost.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/shape/shape.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_distance.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_transformer.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/autocalib.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/blenders.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/camera.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/exposure_compensate.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/matchers.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/motion_estimators.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/seam_finders.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/timelapsers.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util_inl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers_inl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/stitching/warpers.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/superres.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/superres/optical_flow.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/video.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/video/background_segm.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/video/tracking.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/video/tracking_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/video/video.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videoio.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videoio/cap_ios.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio_c.h (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/deblurring.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching_inl.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/frame_source.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/global_motion.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/inpainting.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/log.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_core.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_stabilizing.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/optical_flow.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/outlier_rejection.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/ring_buffer.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/stabilizer.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/videostab/wobble_suppression.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/include/opencv2/world.hpp (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/libPNG.tps (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/libTiff.tps (100%) rename Engine/Plugins/{Runtime/MixedRealityCaptureFramework => Compositing/OpenCVLensDistortion}/Source/ThirdParty/OpenCV/zlib.tps (100%) create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorSelectionModifiers.cpp create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorUtilities.cpp rename Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/{ => Public}/MeshEditorAssetContainer.h (99%) create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorSelectionModifiers.h rename Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/{ => Public}/MeshEditorStaticMeshAdapter.h (97%) rename Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/{ => Public}/MeshEditorStyle.h (93%) create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorUtilities.h rename Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/{ => Public}/OverlayComponent.h (98%) rename Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/{ => Public}/WireframeMeshComponent.h (96%) create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/AssignMaterial.cpp create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/DeleteMeshElement.cpp create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/FlipPolygon.cpp create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/AssignMaterial.h rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/BevelOrInsetPolygon.h (96%) create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/DeleteMeshElement.h rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/EditVertexOrEdgeSharpness.h (95%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/ExtendEdge.h (93%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/ExtendVertex.h (93%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/ExtrudePolygon.h (96%) create mode 100644 Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/FlipPolygon.h rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/HardenOrSoftenEdge.h (92%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/InsertEdgeLoop.h (92%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/QuadrangulateMesh.h (86%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/RemoveEdge.h (91%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/RemoveVertex.h (91%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/SplitEdge.h (91%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/SplitPolygon.h (96%) rename Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/{ => Public}/TessellatePolygon.h (86%) create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/MediaFrameworkUtilities.uplugin create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/MediaFrameworkUtilities.Build.cs create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaBundle.cpp create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaBundleActorBase.cpp create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaFrameworkUtilitiesModule.cpp create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Public/MediaBundle.h create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Public/MediaBundleActorBase.h create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/MediaFrameworkUtilitiesEditor.Build.cs create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleActorDetails.cpp create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleActorDetails.h create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleFactoryNew.cpp create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleFactoryNew.h create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesEditorModule.cpp create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesPlacement.cpp create mode 100644 Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesPlacement.h delete mode 100644 Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcCalibrationModule.cpp delete mode 100644 Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcOpenCVCalibrator.cpp delete mode 100644 Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcOpenCVCalibrator.h delete mode 100644 Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Public/IMrcCalibrationModule.h delete mode 100644 Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MrcLensDistortion.cpp delete mode 100644 Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcLensDistortion.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/DisplayCluster.Build.cs create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintAPIImpl.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintAPIImpl.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintLib.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlBase.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlBase.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlBase.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlBase.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlStandalone.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlStandalone.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/IPDisplayClusterNodeController.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/DisplayClusterClusterManager.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/DisplayClusterClusterManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/IPDisplayClusterClusterManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Checker/DisplayClusterConfigChecker.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Checker/DisplayClusterConfigChecker.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigManager.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigTypes.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/IPDisplayClusterConfigManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParser.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParser.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserDebugAuto.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserDebugAuto.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserText.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserText.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserXml.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserXml.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/IDisplayClusterConfigParserListener.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterBuildConfig.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterConstants.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterGlobals.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterGlobals.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterModule.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterModule.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterStrings.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameEngine.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameMode.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameModeDefault.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterHUD.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterPlayerController.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterCameraComponent.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterPawn.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterPawnDefault.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponent.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSync.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSyncParent.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSyncThis.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterScreenComponent.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSettings.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/DisplayClusterGameManager.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/DisplayClusterGameManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/IPDisplayClusterGameManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/IPDisplayCluster.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/IPDisplayClusterManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/DisplayClusterInputDeviceBase.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/DisplayClusterInputDeviceTraits.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/IDisplayClusterInputDevice.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputData.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDataHolder.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDataHolder.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputData.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDataHolder.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDataHolder.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputData.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDataHolder.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDataHolder.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/DisplayClusterInputManager.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/DisplayClusterInputManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/IPDisplayClusterInputManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterAppExit.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterAppExit.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterBarrier.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterBarrier.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterHelpers.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterLog.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterLog.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterTypesConverter.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterClient.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterClient.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterMessage.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterMessage.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterServer.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterServer.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSession.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSession.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSocketOps.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSocketOps.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterTcpListener.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterTcpListener.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/IDisplayClusterSessionListener.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Protocol/IPDisplayClusterClusterSyncProtocol.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Protocol/IPDisplayClusterSwapSyncProtocol.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncClient.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncClient.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncMsg.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncService.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncService.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/DisplayClusterService.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/DisplayClusterService.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncClient.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncClient.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncMsg.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncService.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncService.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Debug/DisplayClusterDeviceDebug.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Debug/DisplayClusterDeviceDebug.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceBase.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceBase.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceInternals.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceInternals.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterViewportArea.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoBase.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoBase.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/DisplayClusterRenderManager.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/DisplayClusterRenderManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/IPDisplayClusterRenderManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Blueprints/DisplayClusterBlueprintLib.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Blueprints/IDisplayClusterBlueprintAPI.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Cluster/IDisplayClusterClusterManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Cluster/IDisplayClusterClusterSyncObject.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Config/DisplayClusterConfigTypes.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Config/IDisplayClusterConfigManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterCameraComponent.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameEngine.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameMode.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameModeDefault.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterHUD.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterOperationMode.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPawn.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPawnDefault.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPlayerController.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponent.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSync.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSyncParent.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSyncThis.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterScreenComponent.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSettings.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Game/IDisplayClusterGameManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayCluster.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayClusterSerializable.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayClusterStringSerializable.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Input/IDisplayClusterInputManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Render/IDisplayClusterRenderManager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Render/IDisplayClusterStereoDevice.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/DisplayClusterEditor.Build.cs create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditor.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorEngine.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorEngine.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorLog.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorLog.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorSettings.cpp create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Public/DisplayClusterEditor.h create mode 100644 Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Public/DisplayClusterEditorSettings.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/quat.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Analog.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Analog_Output.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Assert.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Auxiliary_Logger.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_BaseClass.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Button.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Configure.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Connection.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Dial.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_EndpointContainer.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FileConnection.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FileController.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_ForceDevice.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Forwarder.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_ForwarderController.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FunctionGenerator.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Imager.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_LamportClock.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Mutex.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Poser.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_RedundantTransmission.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Serial.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_SerialPort.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Shared.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_SharedObject.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Sound.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Text.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Thread.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Tracker.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Types.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_WindowsH.h create mode 100644 Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/VRPN.tps create mode 100644 Engine/Plugins/Runtime/nDisplay/nDisplay.uplugin create mode 100644 Engine/Source/Editor/ContentBrowser/Private/SMetaDataView.cpp create mode 100644 Engine/Source/Editor/ContentBrowser/Private/SMetaDataView.h create mode 100644 Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.cpp create mode 100644 Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.h create mode 100644 Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderActorGroup.cpp rename Engine/Source/Editor/SequenceRecorder/{Private => Public}/SequenceRecorderActorGroup.h (83%) create mode 100644 Engine/Source/Programs/nDisplayLauncher/App.config create mode 100644 Engine/Source/Programs/nDisplayLauncher/App.xaml create mode 100644 Engine/Source/Programs/nDisplayLauncher/App.xaml.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/AppLog/AppLogger.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/BaseInput.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/Camera.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/ClusterNode.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/ConfigItem.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/DynamicCamera.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/MasterNode.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/Parser.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/SceneNode.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/SceneNodeView.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/Screen.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/TrackerInput.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/VRConfig.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Config/Viewport.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/MainWindow.xaml create mode 100644 Engine/Source/Programs/nDisplayLauncher/MainWindow.xaml.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Other/ValidationRules.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Properties/AssemblyInfo.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Properties/Resources.Designer.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Properties/Resources.resx create mode 100644 Engine/Source/Programs/nDisplayLauncher/Properties/Settings.Designer.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Properties/Settings.settings create mode 100644 Engine/Source/Programs/nDisplayLauncher/Properties/app.manifest create mode 100644 Engine/Source/Programs/nDisplayLauncher/Runner/Runner.DeployApp.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Runner/Runner.KillApp.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Runner/Runner.StartApp.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Runner/Runner.StartListeners.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Runner/Runner.StatusListeners.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Runner/Runner.StopListeners.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Runner/Runner.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/Settings/RegistrySaver.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/UIControl/AutoScroller.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/UIControl/UnselectableListBox.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/ValueConversion/SizeConverter.cs create mode 100644 Engine/Source/Programs/nDisplayLauncher/nDisplayLauncher.csproj create mode 100644 Engine/Source/Programs/nDisplayListener/App.config create mode 100644 Engine/Source/Programs/nDisplayListener/Properties/AssemblyInfo.cs create mode 100644 Engine/Source/Programs/nDisplayListener/Properties/app.manifest create mode 100644 Engine/Source/Programs/nDisplayListener/nDisplayListener.cs create mode 100644 Engine/Source/Programs/nDisplayListener/nDisplayListener.csproj create mode 100644 Engine/Source/Runtime/MediaIOCore/Private/Shared/MediaIOCoreEncodeTime.cpp create mode 100644 Engine/Source/Runtime/MediaIOCore/Public/MediaIOCoreEncodeTime.h create mode 100644 Engine/Source/Runtime/TimeManagement/Private/TimeSynchronizationSource.cpp create mode 100644 Templates/TP_nDisplay/Config/DefaultEditor.ini create mode 100644 Templates/TP_nDisplay/Config/DefaultEngine.ini create mode 100644 Templates/TP_nDisplay/Config/DefaultGame.ini create mode 100644 Templates/TP_nDisplay/Config/DefaultInput.ini create mode 100644 Templates/TP_nDisplay/Config/TemplateDefs.ini create mode 100644 Templates/TP_nDisplay/TP_nDisplay.uproject diff --git a/Engine/Build/Commit.gitdeps.xml b/Engine/Build/Commit.gitdeps.xml index 80597794728a..19954f6aeee0 100644 --- a/Engine/Build/Commit.gitdeps.xml +++ b/Engine/Build/Commit.gitdeps.xml @@ -63,7 +63,7 @@ - + @@ -15715,7 +15715,7 @@ - + @@ -15726,27 +15726,27 @@ - + - + - + - + - + @@ -25701,6 +25701,15 @@ + + + + + + + + + @@ -25723,12 +25732,19 @@ - - + + - - + + + + + + + + + @@ -25753,10 +25769,10 @@ - - - - + + + + @@ -25941,7 +25957,7 @@ - + @@ -27252,7 +27268,7 @@ - + @@ -27767,6 +27783,10 @@ + + + + @@ -27778,6 +27798,9 @@ + + + @@ -27801,7 +27824,8 @@ - + + @@ -28581,10 +28605,6 @@ - - - - @@ -28600,11 +28620,20 @@ - - - - + + + + + + + + + + + + + @@ -28808,6 +28837,8 @@ + + @@ -28947,7 +28978,7 @@ - + @@ -38846,6 +38877,12 @@ + + + + + + @@ -39112,7 +39149,6 @@ - @@ -39622,6 +39658,7 @@ + @@ -40017,6 +40054,7 @@ + @@ -40095,6 +40133,7 @@ + @@ -40373,6 +40412,7 @@ + @@ -40801,6 +40841,7 @@ + @@ -40985,6 +41026,7 @@ + @@ -41196,7 +41238,6 @@ - @@ -41233,7 +41274,6 @@ - @@ -41301,7 +41341,6 @@ - @@ -42017,6 +42056,7 @@ + @@ -42042,6 +42082,7 @@ + @@ -42927,6 +42968,7 @@ + @@ -43397,7 +43439,6 @@ - @@ -43836,7 +43877,7 @@ - + @@ -45053,6 +45094,7 @@ + @@ -45114,6 +45156,7 @@ + @@ -45800,6 +45843,7 @@ + @@ -45963,6 +46007,7 @@ + @@ -45999,6 +46044,7 @@ + @@ -46354,6 +46400,7 @@ + @@ -46857,7 +46904,7 @@ - + @@ -46965,6 +47012,7 @@ + @@ -47720,6 +47768,7 @@ + @@ -48106,7 +48155,7 @@ - + @@ -48775,12 +48824,13 @@ + - + @@ -49260,6 +49310,7 @@ + @@ -49661,6 +49712,7 @@ + @@ -49741,6 +49793,7 @@ + @@ -49926,7 +49979,7 @@ - + @@ -50136,7 +50189,6 @@ - @@ -50183,7 +50235,7 @@ - + @@ -50266,6 +50318,7 @@ + @@ -50345,6 +50398,7 @@ + @@ -50365,6 +50419,7 @@ + @@ -51798,6 +51853,7 @@ + @@ -52225,7 +52281,6 @@ - @@ -53084,7 +53139,6 @@ - @@ -53596,6 +53650,7 @@ + @@ -53869,7 +53924,6 @@ - @@ -54333,6 +54387,7 @@ + @@ -54584,6 +54639,7 @@ + @@ -54603,6 +54659,7 @@ + @@ -54676,6 +54733,7 @@ + @@ -55100,7 +55158,7 @@ - + @@ -55308,6 +55366,7 @@ + @@ -56316,6 +56375,7 @@ + @@ -56382,6 +56442,7 @@ + @@ -56642,6 +56703,7 @@ + @@ -56885,6 +56947,7 @@ + @@ -57449,6 +57512,7 @@ + @@ -57642,6 +57706,7 @@ + @@ -57825,6 +57890,7 @@ + @@ -58177,7 +58243,6 @@ - @@ -58583,6 +58648,7 @@ + @@ -58652,7 +58718,6 @@ - @@ -60233,6 +60298,7 @@ + @@ -60964,6 +61030,7 @@ + @@ -61742,6 +61809,7 @@ + @@ -62400,7 +62468,6 @@ - @@ -62753,6 +62820,7 @@ + @@ -63395,7 +63463,6 @@ - @@ -63750,8 +63817,10 @@ + + @@ -64721,6 +64790,7 @@ + @@ -64922,7 +64992,7 @@ - + @@ -65756,8 +65826,8 @@ - + @@ -66123,7 +66193,6 @@ - @@ -66300,7 +66369,6 @@ - @@ -66353,6 +66421,7 @@ + @@ -66508,8 +66577,10 @@ + + @@ -66658,6 +66729,7 @@ + @@ -66725,6 +66797,7 @@ + @@ -67114,6 +67187,7 @@ + @@ -67156,7 +67230,6 @@ - @@ -67477,7 +67550,6 @@ - @@ -67521,6 +67593,7 @@ + @@ -67698,7 +67771,6 @@ - @@ -68405,6 +68477,7 @@ + @@ -68878,6 +68951,7 @@ + @@ -68998,7 +69072,6 @@ - diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/OpenCVLensDistortion.uplugin b/Engine/Plugins/Compositing/OpenCVLensDistortion/OpenCVLensDistortion.uplugin new file mode 100644 index 000000000000..c2178d8cf06c --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/OpenCVLensDistortion.uplugin @@ -0,0 +1,50 @@ +{ + "FileVersion" : 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName" : "OpenCV Lens Distortion", + "Description" : "Plugin to handle camera calibration and lens distortion/undistortion displacement map generation using OpenCV.", + "Category" : "Compositing", + "CreatedBy" : "Epic Games, Inc.", + "CreatedByURL" : "http://epicgames.com", + "DocsURL" : "", + "MarketplaceURL" : "", + "SupportURL" : "", + "EnabledByDefault" : false, + "CanContainContent" : true, + "IsBetaVersion" : true, + "Installed" : false, + "Modules" : + [ + { + "Name" : "OpenCVLensCalibration", + "Type" : "Runtime", + "LoadingPhase": "PostConfigInit", + "WhitelistPlatforms": [ + "Win64", + "Win32", + "Linux" + ] + }, + { + "Name" : "OpenCVLensDistortion", + "Type" : "Runtime", + "LoadingPhase": "PostConfigInit", + "WhitelistPlatforms": [ + "Win64", + "Win32", + "Linux" + ] + }, + { + "Name": "OpenCVHelper", + "Type": "Runtime", + "LoadingPhase": "PostConfigInit", + "WhitelistPlatforms": [ + "Win64", + "Win32", + "Linux" + ] + } + ] +} \ No newline at end of file diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Shaders/Private/DisplacementMapGeneration.usf b/Engine/Plugins/Compositing/OpenCVLensDistortion/Shaders/Private/DisplacementMapGeneration.usf new file mode 100644 index 000000000000..7e2ce2c8837a --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Shaders/Private/DisplacementMapGeneration.usf @@ -0,0 +1,97 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +/*============================================================================= + DisplacementMapGenerator.usf: Generate lens distortion and undistortion + UV displacement map into a render target from precomputed undistortion + UV displacement map. + + The pixel shader directly computes the distort viewport UV to undistort + viewport UV displacment using Sv_Position and the pre-computed map and + store them into the red and green channels. + + However to avoid resolving with a ferrari method, or doing a newton method + on the GPU to compute the undistort viewport UV to distort viewport UV + displacement, this couple of shaders works as follow: The vertex shader + undistort the grid's vertices, and passdown to the pixel shader the viewport + UV of where they should have been on screen without undistortion. The pixel + shader can then generate the undistort viewport UV to distort viewport UV + displacement by just subtracting the pixel's viewport UV. +=============================================================================*/ + +#include "/Engine/Public/Platform.ush" +#include "/Engine/Private/Common.ush" + + +// Size of the pixels in the viewport UV coordinates. +float2 PixelUVSize; + +// Pre computed undistort displacement map +Texture2D UndistortDisplacementMap; + +//Sampler to use with pre computed map +SamplerState BilinearClampedSampler; + + +// Flip UV's y component. +float2 FlipUV(float2 UV) +{ + return float2(UV.x, 1 - UV.y); +} + +float2 ComputeGridVertexUV(uint GlobalVertexId) +{ + // Compute the cell index. + uint GridCellIndex = GlobalVertexId / 6; + + // Compute row and column id of the cell within the grid. + uint GridColumnId = GridCellIndex / GRID_SUBDIVISION_Y; + uint GridRowId = GridCellIndex - GridColumnId * GRID_SUBDIVISION_Y; + + // Compute the vertex id within a 2 triangles grid cell. + uint VertexId = GlobalVertexId - GridCellIndex * 6; + + // Compute the bottom left originated UV coordinate of the triangle's vertex within the cell. + float2 CellVertexUV = float2(0x1 & ((VertexId + 1) / 3), VertexId & 0x1); + + // Compute the top left originated UV of the vertex within the grid. + float2 GridInvSize = 1.f / float2(GRID_SUBDIVISION_X, GRID_SUBDIVISION_Y); + float2 GridVertexUV = FlipUV(GridInvSize * (CellVertexUV + float2(GridColumnId, GridRowId))); + + return GridVertexUV; +} + +void MainVS(in uint GlobalVertexId : SV_VertexID + , out float2 OutVertexDistortedViewportUV : TEXCOORD0 + , out float4 OutPosition : SV_POSITION) +{ + //Get top left originated UV of the vertex from the distorted space + float2 GridVertexUV = ComputeGridVertexUV(GlobalVertexId); + + // Fetch the undistort UV from precomputed map + float2 UndistortUV = UndistortDisplacementMap.SampleLevel(BilinearClampedSampler, GridVertexUV, 0).xy + GridVertexUV; + + // Output vertex position. + OutPosition = float4(FlipUV(UndistortUV) * 2 - 1, 0, 1); + + // Output top left originated UV of the vertex. + OutVertexDistortedViewportUV = GridVertexUV; +} + + +void MainPS(in noperspective float2 VertexDistortedViewportUV : TEXCOORD0 + , in float4 SvPosition : SV_POSITION + , out float4 OutColor : SV_Target0) +{ + // Compute the pixel's top left originated UV. + float2 ViewportUV = SvPosition.xy * PixelUVSize; + + // Fetch the undistort UV from precomputed map + float2 UndistortUV = UndistortDisplacementMap.SampleLevel(BilinearClampedSampler, ViewportUV, 0).xy + ViewportUV; + + //Compute both displacement UVs + float2 DistortUVtoUndistortUV = UndistortUV - ViewportUV; + float2 UndistortUVtoDistortUV = VertexDistortedViewportUV - ViewportUV; + + // Output displacement channels. + OutColor = float4(DistortUVtoUndistortUV, UndistortUVtoDistortUV); +} diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/OpenCVHelper.Build.cs b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/OpenCVHelper.Build.cs similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/OpenCVHelper.Build.cs rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/OpenCVHelper.Build.cs diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/Private/OpenCVHelperModule.cpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/Private/OpenCVHelperModule.cpp similarity index 96% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/Private/OpenCVHelperModule.cpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/Private/OpenCVHelperModule.cpp index b40ad32cefb6..e51f314654f5 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/Private/OpenCVHelperModule.cpp +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/Private/OpenCVHelperModule.cpp @@ -25,7 +25,7 @@ FOpenCVHelperModule::FOpenCVHelperModule() void FOpenCVHelperModule::StartupModule() { - const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT("MixedRealityCaptureFramework"))->GetBaseDir(); + const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT("OpenCVLensDistortion"))->GetBaseDir(); #if WITH_OPENCV const FString OpenCvBinPath = PluginDir / TEXT(PREPROCESSOR_TO_STRING(OPENCV_PLATFORM_PATH)); diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/Public/IOpenCVHelperModule.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/Public/IOpenCVHelperModule.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/Public/IOpenCVHelperModule.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/Public/IOpenCVHelperModule.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/Public/OpenCVHelper.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/Public/OpenCVHelper.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/OpenCVHelper/Public/OpenCVHelper.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVHelper/Public/OpenCVHelper.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/MixedRealityCaptureCalibration.Build.cs b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/OpenCVLensCalibration.Build.cs similarity index 60% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/MixedRealityCaptureCalibration.Build.cs rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/OpenCVLensCalibration.Build.cs index 4e017f3546ee..c74aa2a58312 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/MixedRealityCaptureCalibration.Build.cs +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/OpenCVLensCalibration.Build.cs @@ -2,19 +2,18 @@ using UnrealBuildTool; -public class MixedRealityCaptureCalibration : ModuleRules +public class OpenCVLensCalibration : ModuleRules { - public MixedRealityCaptureCalibration(ReadOnlyTargetRules Target) : base(Target) + public OpenCVLensCalibration(ReadOnlyTargetRules Target) : base(Target) { PublicIncludePaths.AddRange( new string[] { - } + } ); PrivateIncludePaths.AddRange( new string[] { - "MixedRealityCaptureCalibration/Private" - } + } ); PublicDependencyModuleNames.AddRange( @@ -27,12 +26,9 @@ public class MixedRealityCaptureCalibration : ModuleRules "Core", "CoreUObject", "Engine", - "MixedRealityCaptureFramework", - "MediaAssets", - "InputCore", "OpenCVHelper", "OpenCV", - "HeadMountedDisplay" + "OpenCVLensDistortion", } ); } diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrationModule.cpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrationModule.cpp new file mode 100644 index 000000000000..aabc1e5bebfd --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrationModule.cpp @@ -0,0 +1,19 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "IOpenCVLensCalibrationModule.h" + +#include "Logging/LogMacros.h" + + +DEFINE_LOG_CATEGORY(LogOpenCVLensCalibration) + +////////////////////////////////////////////////////////////////////////// +// FOpenCVLensCalibrationModule +class FOpenCVLensCalibrationModule : public IOpenCVLensCalibrationModule +{ + +}; + +////////////////////////////////////////////////////////////////////////// + +IMPLEMENT_MODULE(FOpenCVLensCalibrationModule, OpenCVLensCalibration) diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrator.cpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrator.cpp new file mode 100644 index 000000000000..9757f14012fc --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrator.cpp @@ -0,0 +1,287 @@ +// Copyright 1998-2017 Epic Games, Inc. All Rights Reserved. + +#include "OpenCVLensCalibrator.h" + +#include "Engine/TextureRenderTarget2D.h" +#include "IOpenCVLensCalibrationModule.h" +#include "Logging/LogMacros.h" +#include "PixelFormat.h" +//#include "UnrealClient.h" + +#if WITH_OPENCV +OPENCV_INCLUDES_START +#undef check // the check macro causes problems with opencv headers +#include "opencv2/calib3d.hpp" +#include "opencv2/imgproc.hpp" +OPENCV_INCLUDES_END +#endif + + +UOpenCVLensCalibrator* UOpenCVLensCalibrator::CreateCalibrator(int32 InBoardWidth /*= 7*/, int32 InBoardHeight /*= 5*/, float InSquareSize /*= 3.f*/, bool bInUseFisheyeModel /*= false*/) +{ +#if WITH_OPENCV == 0 + UE_LOG(LogOpenCVLensCalibration, Error, TEXT("OpenCV isn't enabled. Calibration won`t work as expected.")); +#endif + + UOpenCVLensCalibrator* Result = NewObject(); + Result->Reset(InBoardWidth, InBoardHeight, InSquareSize, bInUseFisheyeModel); + return Result; +} + +void UOpenCVLensCalibrator::Reset(int32 InBoardWidth, int32 InBoardHeight, float InSquareSize/*=3.f*/, bool bInUseFisheyeModel /*= false*/) +{ + SquareSize = InSquareSize; + bUseFisheyeModel = bInUseFisheyeModel; + MinimumCornerCoordinates = FVector2D(FLT_MAX, FLT_MAX); + MaximumCornerCoordinates = FVector2D(FLT_MIN, FLT_MIN); + +#if WITH_OPENCV + BoardSize = cv::Size(InBoardWidth, InBoardHeight); + // Assuming the chessboard is at the origin lying flat on the z plane, construct object coordinates for it + BoardPoints.clear(); + BoardPoints.reserve(BoardSize.height * BoardSize.width); + for (int i = 0; i < BoardSize.height; ++i) + { + for (int j = 0; j < BoardSize.width; ++j) + { + BoardPoints.push_back(cv::Point3f(float(j*SquareSize), float(i*SquareSize), 0)); + } + } + + // Reserve space for a few samples + ImagePoints.clear(); + ImagePoints.reserve(25); +#endif +} + +bool UOpenCVLensCalibrator::FeedRenderTarget(UTextureRenderTarget2D* InTextureRT) +{ +#if WITH_OPENCV + if (InTextureRT == nullptr) + { + UE_LOG(LogOpenCVLensCalibration, Error, TEXT("Invalid render target was fed to LensCalibrator")); + return false; + } + + TArray RawData; + FRenderTarget* RenderTarget = InTextureRT->GameThread_GetRenderTargetResource(); + const EPixelFormat Format = InTextureRT->GetFormat(); + bool bReadSuccess = false; + switch (Format) + { + case PF_FloatRGBA: + { + TArray FloatColors; + bReadSuccess = RenderTarget->ReadFloat16Pixels(FloatColors); + if (bReadSuccess) + { + RawData.AddUninitialized(FloatColors.Num() * 3); // We need to convert float to uint8 + for (int I = 0; I < FloatColors.Num(); I++) + { + // OpenCV expects BGR ordering and we're ignoring the alpha component + RawData[I * 3 + 0] = (uint8)(FloatColors[I].B*255.0f); + RawData[I * 3 + 1] = (uint8)(FloatColors[I].G*255.0f); + RawData[I * 3 + 2] = (uint8)(FloatColors[I].R*255.0f); + } + } + + break; + } + case PF_B8G8R8A8: + { + TArray Colors; + bReadSuccess = RenderTarget->ReadPixels(Colors); + if (bReadSuccess) + { + RawData.AddUninitialized(Colors.Num() * 3); + for (int I = 0; I < Colors.Num(); I++) + { + // Strip out the alpha component for OpenCV + RawData[I * 3 + 0] = Colors[I].B; + RawData[I * 3 + 1] = Colors[I].G; + RawData[I * 3 + 2] = Colors[I].R; + } + } + + break; + } + default: + { + UE_LOG(LogOpenCVLensCalibration, Warning, TEXT("Invalid pixel format in render target %s"), *InTextureRT->GetName()); + break; + } + } + + if (bReadSuccess == false) + { + // Either invalid texture data or unsupported texture format + return false; + } + + cv::Mat Image(InTextureRT->SizeY, InTextureRT->SizeX, CV_8UC3, (void*)RawData.GetData()); + return Feed(Image); +#else + return false; +#endif +} + +bool UOpenCVLensCalibrator::FeedImage(const FString& FilePath) +{ +#if WITH_OPENCV + const FTCHARToUTF8 FilePathUtf8(*FilePath); + const cv::String CVPath(FilePathUtf8.Get(), FilePathUtf8.Length()); + + cv::Mat RGBImage = cv::imread(CVPath, cv::IMREAD_UNCHANGED); + if (RGBImage.empty() == false) + { + cv::Mat BGRImage; + cv::cvtColor(RGBImage, BGRImage, cv::COLOR_RGBA2BGR); + return Feed(BGRImage); + } + else + { + return false; + } +#else + return false; +#endif +} + +#if WITH_OPENCV +bool UOpenCVLensCalibrator::Feed(const cv::Mat& InImage) +{ + //Validate image size before going further + ImageSize = InImage.size(); + if (ImageSize.empty()) + { + return false; + } + + std::vector Corners; + Corners.reserve(BoardSize.height * BoardSize.width); + + //Using flag CV_CALIB_CB_FAST_CHECK is faster but didn`t catch corners on some images. + cv::Mat Gray; + cv::cvtColor(InImage, Gray, CV_BGR2GRAY); + const bool bFound = cv::findChessboardCorners(Gray, BoardSize, Corners, CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_NORMALIZE_IMAGE); + if (bFound) + { + cv::cornerSubPix(Gray, Corners, cv::Size(11, 11), cv::Size(-1, -1), cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.001)); + ImagePoints.push_back(Corners); + + //Update min/max coordinates to help user cover the whole lens. + for (const cv::Point2f& Point : Corners) + { + MinimumCornerCoordinates.X = FMath::Min(MinimumCornerCoordinates.X, Point.x); + MinimumCornerCoordinates.Y = FMath::Min(MinimumCornerCoordinates.Y, Point.y); + MaximumCornerCoordinates.X = FMath::Max(MaximumCornerCoordinates.X, Point.x); + MaximumCornerCoordinates.Y = FMath::Max(MaximumCornerCoordinates.Y, Point.y); + } + } + + return bFound; +} +#endif + +bool UOpenCVLensCalibrator::CalculateLensParameters(FOpenCVLensDistortionParameters& OutLensDistortionParameters, float& OutMarginOfError, FOpenCVCameraViewInfo& OutCameraViewInfo) +{ +#if WITH_OPENCV + + if (ImagePoints.empty()) + { + return false; + } + + cv::Mat DistortionCoefficients; + cv::Mat CameraMatrix = cv::Mat::eye(3, 3, CV_64F); + OutMarginOfError = FLT_MAX; + { + // Reserve space for Rotation and Translation vectors to compute the total error of our calibration. + std::vector Rvecs, Tvecs; + Rvecs.reserve(ImagePoints.size()); + Tvecs.reserve(ImagePoints.size()); + + // calibrateCamera requires object points for each image capture, even though they're all the same object + // (the chessboard) in all cases. + std::vector> ObjectPoints; + ObjectPoints.resize(ImagePoints.size(), BoardPoints); + + if (bUseFisheyeModel) + { + //fisheye calibration doesn't like it with 1 image + if (ImagePoints.size() > 1) + { + OutMarginOfError = (float)cv::fisheye::calibrate(ObjectPoints, ImagePoints, ImageSize, CameraMatrix, DistortionCoefficients, Rvecs, Tvecs, cv::fisheye::CALIB_RECOMPUTE_EXTRINSIC + cv::fisheye::CALIB_FIX_SKEW, cv::TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 1e-6)); + } + else + { + UE_LOG(LogOpenCVLensCalibration, Warning, TEXT("Fisheye calibration requires at least 2 samples.")); + return false; + } + } + else + { + OutMarginOfError = (float)cv::calibrateCamera(ObjectPoints, ImagePoints, ImageSize, CameraMatrix, DistortionCoefficients, Rvecs, Tvecs); + } + } + + if (!checkRange(CameraMatrix) || !checkRange(DistortionCoefficients)) + { + return false; + } + + // Convert the params to a UE4 struct + { + //Fisheye camera model vs pinhole camera model differs slightly in parameter assignment + //Pinhole can have up to 6 radial distortion parameters and 2 tangential parameters + //where fisheye model only have 4 'k' parameters. + if (bUseFisheyeModel) + { + check(DistortionCoefficients.rows >= 1); + OutLensDistortionParameters.K1 = DistortionCoefficients.at(0); + OutLensDistortionParameters.K2 = DistortionCoefficients.at(1); + OutLensDistortionParameters.K3 = DistortionCoefficients.at(2); + OutLensDistortionParameters.K4 = DistortionCoefficients.at(3); + } + else + { + // The DistortionCoefficients matrix is a one row matrix for pinhole model + check(DistortionCoefficients.rows == 1); + OutLensDistortionParameters.K1 = DistortionCoefficients.at(0); + OutLensDistortionParameters.K2 = DistortionCoefficients.at(1); + OutLensDistortionParameters.P1 = DistortionCoefficients.at(2); + OutLensDistortionParameters.P2 = DistortionCoefficients.at(3); + + // The docs make it sound like third (and fourth and fifth) radial coefficients may be optional + OutLensDistortionParameters.K3 = DistortionCoefficients.cols >= 5 ? DistortionCoefficients.at(4) : .0f; + OutLensDistortionParameters.K4 = DistortionCoefficients.cols >= 6 ? DistortionCoefficients.at(5) : .0f; + OutLensDistortionParameters.K5 = DistortionCoefficients.cols >= 7 ? DistortionCoefficients.at(6) : .0f; + OutLensDistortionParameters.K6 = DistortionCoefficients.cols >= 8 ? DistortionCoefficients.at(7) : .0f; + } + + //Save camera matrix with normalized values + check(CameraMatrix.rows == 3 && CameraMatrix.cols == 3); + OutLensDistortionParameters.F.X = CameraMatrix.at(0, 0) / ImageSize.width; + OutLensDistortionParameters.F.Y = CameraMatrix.at(1, 1) / ImageSize.height; + OutLensDistortionParameters.C.X = CameraMatrix.at(0, 2) / ImageSize.width; + OutLensDistortionParameters.C.Y = CameraMatrix.at(1, 2) / ImageSize.height; + + OutLensDistortionParameters.bUseFisheyeModel = bUseFisheyeModel; + } + + // Estimate camera view information (FOV, Aspect ratio...) + { + double FocalLengthRatio, FovX, FovY, FocalLength_Unused; + cv::Point2d PrincipalPoint_Unused; + // We pass in zero aperture size as it is unknown. (It is only required for calculating focal length and the principal point) + cv::calibrationMatrixValues(CameraMatrix, ImageSize, 0.0, 0.0, FovX, FovY, FocalLength_Unused, PrincipalPoint_Unused, FocalLengthRatio); + OutCameraViewInfo.HorizontalFOV = FovX; + OutCameraViewInfo.VerticalFOV = FovY; + OutCameraViewInfo.FocalLengthRatio = FocalLengthRatio; + } + + return true; +#else + return false; +#endif +} diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrator.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrator.h new file mode 100644 index 000000000000..f5fc73763829 --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Private/OpenCVLensCalibrator.h @@ -0,0 +1,119 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" + +#include "OpenCVLensDistortionParameters.h" + +#if WITH_OPENCV +#include "OpenCVHelper.h" +OPENCV_INCLUDES_START +#undef check // the check macro causes problems with opencv headers +#include "opencv2/core/core.hpp" +#include "opencv2/imgcodecs.hpp" +OPENCV_INCLUDES_END +#endif + +#include "OpenCVLensCalibrator.generated.h" + + +class UTextureRenderTarget2D; + + +UCLASS(meta = (BlueprintSpawnableComponent)) +class UOpenCVLensCalibrator : public UObject +{ + GENERATED_BODY() + +private: + /** + * Default constructor for an OpenCV lens calibration object. + */ + UOpenCVLensCalibrator(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) + : UObject(ObjectInitializer) + {} +public: + + /** + * @param BoardWidth The width of the checkerboard used to calibrate the camera counted as number of inner edges. + * @param BoardHeight The height of the checkerboard used to calibrate the camera counted as number of inner edges. + * @param SquareSize The width of each square in (potentially arbitrary) world units. + * @param bUseFisheyeModel Specifies if the calibrator is to use the fisheye camera model from OpenCV + */ + UFUNCTION(BlueprintCallable, Category = "LensDistortion|OpenCV|Calibration") + static UOpenCVLensCalibrator* CreateCalibrator(int32 BoardWidth = 7, int32 BoardHeight = 5, float SquareSize = 3.f, bool bUseFisheyeModel = false); + + /** + * @param InBoardWidth The width of the checkerboard used to calibrate the camera counted as number of inner edges. + * @param InBoardHeight The height of the checkerboard used to calibrate the camera counted as number of inner edges. + * @param InSquareSize The width of each square in (potentially arbitrary) world units. + * @param bInUseFisheyeModel Specifies if the calibrator is to use the fisheye camera model from OpenCV + */ + void Reset(int32 InBoardWidth, int32 InBoardHeight, float InSquareSize = 3.f, bool bInUseFisheyeModel = false); + + /** + * Feeds a render target to the calibration. It must contain a checkerboard somewhere in the image. + * The images fed in should come from the same camera. + * @return true if the calibrator found a checkerboard in the image. + */ + UFUNCTION(BlueprintCallable, Category = "LensDistortion|OpenCV|Calibration") + bool FeedRenderTarget(UTextureRenderTarget2D* TextureRenderTarget); + + /** + * Feeds an image to the calibration. It must contain a checkerboard somewhere in the image. + * The images fed in should come from the same camera. + * @return true if the calibrator found a checkerboard in the image. + */ + UFUNCTION(BlueprintCallable, Category = "LensDistortion|OpenCV|Calibration") + bool FeedImage(const FString& FilePath); + + /** + * @param LensDistortionParameters the calculated distortion data from the images passed into the calibrator. + * @param MarginOfError returned reprojection error of the calibration + * @param CameraViewInfo returned information about the camera view obtained from calibration parameters + * @return true if there was enough data to calculate the distortion + */ + UFUNCTION(BlueprintCallable, Category = "LensDistortion|OpenCV|Calibration") + bool CalculateLensParameters(FOpenCVLensDistortionParameters& LensDistortionParameters, float& MarginOfError, FOpenCVCameraViewInfo& CameraViewInfo); + +private: + + /** + * @param InImage The input image in matrix form formatted as BGR. + * @return true if checkerboard corners were found. + */ +#if WITH_OPENCV + bool Feed(const cv::Mat& InImage); +#endif + +public: + /** Smallest coordinates of a grid corner that was found. For best result, try to cover full resolution of the input. */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Calibration") + FVector2D MinimumCornerCoordinates; + + /** Biggest coordinates of a grid corner that was found. For best result, try to cover full resolution of the input. */ + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "Calibration") + FVector2D MaximumCornerCoordinates; + +private: + +#if WITH_OPENCV + /** Size of a square in the grid. Potentially useless. */ + std::vector> ImagePoints; + + /** Size of a square in the grid. Potentially useless. */ + std::vector BoardPoints; + + /** Size of input image used for calibration in pixels. */ + cv::Size ImageSize; + + /** Size of the checkerboard in number of squares. */ + cv::Size BoardSize; +#endif + + /** Size of a square in the grid. Potentially useless. */ + float SquareSize; + + /** Specifies if Fisheye camera model is to be used. */ + bool bUseFisheyeModel; +}; \ No newline at end of file diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Public/IOpenCVLensCalibrationModule.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Public/IOpenCVLensCalibrationModule.h new file mode 100644 index 000000000000..33cf0390260b --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensCalibration/Public/IOpenCVLensCalibrationModule.h @@ -0,0 +1,45 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +#define OPENCV_LENSCALIBRATION_MODULE_NAME TEXT("OpenCVLensCalibration") + +DECLARE_LOG_CATEGORY_EXTERN(LogOpenCVLensCalibration, Verbose, All); + +/** + * The public interface to this module + */ +class IOpenCVLensCalibrationModule : public IModuleInterface +{ + +public: + + /** + * 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 IOpenCVLensCalibrationModule& Get() + { + return FModuleManager::LoadModuleChecked(OPENCV_LENSCALIBRATION_MODULE_NAME); + } + + /** + * 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(OPENCV_LENSCALIBRATION_MODULE_NAME); + } +}; + +#undef OPENCV_LENSCALIBRATION_MODULE_NAME + diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/OpenCVLensDistortion.Build.cs b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/OpenCVLensDistortion.Build.cs new file mode 100644 index 000000000000..32130982f718 --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/OpenCVLensDistortion.Build.cs @@ -0,0 +1,45 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class OpenCVLensDistortion : ModuleRules + { + public OpenCVLensDistortion(ReadOnlyTargetRules Target) : base(Target) + { + PublicIncludePaths.AddRange( + new string[] { + // ... add public include paths required here ... + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // ... add other private include paths required here ... + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + + // ... add other public dependencies that you statically link with here ... + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "OpenCVHelper", + "OpenCV", + "RenderCore", + "RHI", + "ShaderCore", + // ... add private dependencies that you statically link with here ... + } + ); + } + } +} diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/LensDistortionDisplacementMapRendering.cpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/LensDistortionDisplacementMapRendering.cpp new file mode 100644 index 000000000000..24288df0c29d --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/LensDistortionDisplacementMapRendering.cpp @@ -0,0 +1,194 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "OpenCVLensDistortionParameters.h" + +#include "Engine/Texture2D.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Engine/World.h" +#include "GlobalShader.h" +#include "IOpenCVLensDistortionModule.h" +#include "Logging/LogMacros.h" +#include "PipelineStateCache.h" +#include "RHIStaticStates.h" +#include "SceneUtils.h" +#include "SceneInterface.h" +#include "ShaderParameterUtils.h" + + +//Parameters for the grid we'll use to get the reciprocal of our undistortion map +static const uint32 kGridSubdivisionX = 32; +static const uint32 kGridSubdivisionY = 16; + +#if WITH_OPENCV +class FLensDistortionDisplacementMapGenerationShader : public FGlobalShader +{ +public: + static bool ShouldCompilePermutation(const FGlobalShaderPermutationParameters& Parameters) + { + return IsFeatureLevelSupported(Parameters.Platform, ERHIFeatureLevel::SM4); + } + + static void ModifyCompilationEnvironment(const FGlobalShaderPermutationParameters& Parameters, FShaderCompilerEnvironment& OutEnvironment) + { + FGlobalShader::ModifyCompilationEnvironment(Parameters, OutEnvironment); + OutEnvironment.SetDefine(TEXT("GRID_SUBDIVISION_X"), kGridSubdivisionX); + OutEnvironment.SetDefine(TEXT("GRID_SUBDIVISION_Y"), kGridSubdivisionY); + } + + FLensDistortionDisplacementMapGenerationShader() {} + + FLensDistortionDisplacementMapGenerationShader(const ShaderMetaType::CompiledShaderInitializerType& Initializer) + : FGlobalShader(Initializer) + { + PixelUVSize.Bind(Initializer.ParameterMap, TEXT("PixelUVSize")); + UndistortDisplacementMap.Bind(Initializer.ParameterMap, TEXT("UndistortDisplacementMap")); + BilinearSampler.Bind(Initializer.ParameterMap, TEXT("BilinearSampler")); + } + + template + void SetParameters(FRHICommandListImmediate& RHICmdList, const TShaderRHIParamRef ShaderRHI, const FTextureResource* PreComputedDisplacementMap, const FIntPoint& DisplacementMapResolution) + { + FVector2D PixelUVSizeValue(1.f / float(DisplacementMapResolution.X), 1.f / float(DisplacementMapResolution.Y)); + + SetShaderValue(RHICmdList, ShaderRHI, PixelUVSize, PixelUVSizeValue); + SetTextureParameter(RHICmdList, ShaderRHI, UndistortDisplacementMap, BilinearSampler, TStaticSamplerState::GetRHI(), PreComputedDisplacementMap->TextureRHI); + } + + virtual bool Serialize(FArchive& Ar) override + { + bool bShaderHasOutdatedParameters = FGlobalShader::Serialize(Ar); + Ar << PixelUVSize << UndistortDisplacementMap << BilinearSampler; + return bShaderHasOutdatedParameters; + } + +private: + FShaderParameter PixelUVSize; + FShaderResourceParameter UndistortDisplacementMap; + FShaderResourceParameter BilinearSampler; +}; + + +class FLensDistortionDisplacementMapGenerationVS : public FLensDistortionDisplacementMapGenerationShader +{ + DECLARE_SHADER_TYPE(FLensDistortionDisplacementMapGenerationVS, Global); + +public: + + /** Default constructor. */ + FLensDistortionDisplacementMapGenerationVS() {} + + /** Initialization constructor. */ + FLensDistortionDisplacementMapGenerationVS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) + : FLensDistortionDisplacementMapGenerationShader(Initializer) + { + } +}; + + +class FLensDistortionDisplacementMapGenerationPS : public FLensDistortionDisplacementMapGenerationShader +{ + DECLARE_SHADER_TYPE(FLensDistortionDisplacementMapGenerationPS, Global); + +public: + + /** Default constructor. */ + FLensDistortionDisplacementMapGenerationPS() {} + + /** Initialization constructor. */ + FLensDistortionDisplacementMapGenerationPS(const ShaderMetaType::CompiledShaderInitializerType& Initializer) + : FLensDistortionDisplacementMapGenerationShader(Initializer) + { } +}; + + +IMPLEMENT_SHADER_TYPE(, FLensDistortionDisplacementMapGenerationVS, TEXT("/Plugin/OpenCVLensDistortion/Private/DisplacementMapGeneration.usf"), TEXT("MainVS"), SF_Vertex) +IMPLEMENT_SHADER_TYPE(, FLensDistortionDisplacementMapGenerationPS, TEXT("/Plugin/OpenCVLensDistortion/Private/DisplacementMapGeneration.usf"), TEXT("MainPS"), SF_Pixel) +#endif + +static void DrawUVDisplacementToRenderTargetFromPreComputedDisplacementMap_RenderThread(FRHICommandListImmediate& RHICmdList + , const FTextureResource* PreComputedDisplacementMap + , const FName& TextureRenderTargetName + , FTextureRenderTargetResource* OutTextureRenderTargetResource + , ERHIFeatureLevel::Type FeatureLevel) +{ +#if WITH_OPENCV + check(IsInRenderingThread()); + check(PreComputedDisplacementMap); + +#if WANTS_DRAW_MESH_EVENTS + FString EventName; + TextureRenderTargetName.ToString(EventName); + SCOPED_DRAW_EVENTF(RHICmdList, DrawUVDisplacementToRenderTargetFromPreComputedDisplacementMap, TEXT("OpenCVLensDistortionDisplacementMapGeneration %s"), *EventName); +#else + SCOPED_DRAW_EVENT(RHICmdList, DrawUVDisplacementToRenderTargetFromPreComputedDisplacementMap); +#endif + + // Set render target. + SetRenderTarget(RHICmdList, OutTextureRenderTargetResource->GetRenderTargetTexture(), FTextureRHIRef(), ESimpleRenderTargetMode::EClearColorAndDepth, FExclusiveDepthStencil::DepthNop_StencilNop); + + // Update viewport. + const FIntPoint DisplacementMapResolution(OutTextureRenderTargetResource->GetSizeX(), OutTextureRenderTargetResource->GetSizeY()); + RHICmdList.SetViewport(0, 0, 0.f, DisplacementMapResolution.X, DisplacementMapResolution.Y, 1.f); + + // Get shaders. + TShaderMap* GlobalShaderMap = GetGlobalShaderMap(FeatureLevel); + TShaderMapRef< FLensDistortionDisplacementMapGenerationVS > VertexShader(GlobalShaderMap); + TShaderMapRef< FLensDistortionDisplacementMapGenerationPS > PixelShader(GlobalShaderMap); + + // Set the graphic pipeline state. + FGraphicsPipelineStateInitializer GraphicsPSOInit; + RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); + GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState::GetRHI(); + GraphicsPSOInit.BlendState = TStaticBlendState<>::GetRHI(); + GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); + GraphicsPSOInit.PrimitiveType = PT_TriangleList; + GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GetVertexDeclarationFVector4(); + GraphicsPSOInit.BoundShaderState.VertexShaderRHI = GETSAFERHISHADER_VERTEX(*VertexShader); + GraphicsPSOInit.BoundShaderState.PixelShaderRHI = GETSAFERHISHADER_PIXEL(*PixelShader); + SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit); + + // Update shader uniform parameters. + VertexShader->SetParameters(RHICmdList, VertexShader->GetVertexShader(), PreComputedDisplacementMap, DisplacementMapResolution); + PixelShader->SetParameters(RHICmdList, PixelShader->GetPixelShader(), PreComputedDisplacementMap, DisplacementMapResolution); + + // Draw grid. + const uint32 PrimitiveCount = kGridSubdivisionX * kGridSubdivisionY * 2; + RHICmdList.DrawPrimitive(PT_TriangleList, 0, PrimitiveCount, 1); + + // Resolve render target. + RHICmdList.CopyToResolveTarget(OutTextureRenderTargetResource->GetRenderTargetTexture(), OutTextureRenderTargetResource->TextureRHI, FResolveParams()); +#endif +} + +void FOpenCVLensDistortionParameters::DrawDisplacementMapToRenderTarget(UWorld* InWorld, UTextureRenderTarget2D* InOutputRenderTarget, UTexture2D* InPreComputedUndistortDisplacementMap) +{ +#if WITH_OPENCV + check(IsInGameThread()); + + if (!InOutputRenderTarget) + { + UE_LOG(LogOpenCVLensDistortion, Error, TEXT("Invalid render target to draw on.")); + return; + } + + if(InPreComputedUndistortDisplacementMap == nullptr || InPreComputedUndistortDisplacementMap->Resource == nullptr) + { + UE_LOG(LogOpenCVLensDistortion, Error, TEXT("Precomputed displacement map is required to generate final displacement maps.")); + return; + } + + //Prepare parameters for render command + const FName TextureRenderTargetName = InOutputRenderTarget->GetFName(); + FTextureRenderTargetResource* TextureRenderTargetResource = InOutputRenderTarget->GameThread_GetRenderTargetResource(); + const FTextureResource* PreComputedMapResource = InPreComputedUndistortDisplacementMap->Resource; + ERHIFeatureLevel::Type FeatureLevel = InWorld->Scene->GetFeatureLevel(); + + ENQUEUE_RENDER_COMMAND(CaptureCommand) + ( + [PreComputedMapResource, TextureRenderTargetResource, TextureRenderTargetName, FeatureLevel](FRHICommandListImmediate& RHICmdList) + { + DrawUVDisplacementToRenderTargetFromPreComputedDisplacementMap_RenderThread(RHICmdList, PreComputedMapResource, TextureRenderTargetName, TextureRenderTargetResource, FeatureLevel); + } + ); +#endif +} diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionBlueprintLibrary.cpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionBlueprintLibrary.cpp new file mode 100644 index 000000000000..56c04a361582 --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionBlueprintLibrary.cpp @@ -0,0 +1,23 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "OpenCVLensDistortionBlueprintLibrary.h" + +#include "Engine/Texture2D.h" +#include "Engine/TextureRenderTarget2D.h" + +UOpenCVLensDistortionBlueprintLibrary::UOpenCVLensDistortionBlueprintLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ } + + +void UOpenCVLensDistortionBlueprintLibrary::DrawDisplacementMapToRenderTarget(const UObject* WorldContextObject + , UTextureRenderTarget2D* OutputRenderTarget + , UTexture2D* PreComputedUndistortDisplacementMap) +{ + FOpenCVLensDistortionParameters::DrawDisplacementMapToRenderTarget(WorldContextObject->GetWorld(), OutputRenderTarget, PreComputedUndistortDisplacementMap); +} + +UTexture2D* UOpenCVLensDistortionBlueprintLibrary::CreateUndistortUVDisplacementMap(const FOpenCVLensDistortionParameters& LensParameters, const FIntPoint& ImageSize, const float CroppingFactor, FOpenCVCameraViewInfo& CameraViewInfo) +{ + return LensParameters.CreateUndistortUVDisplacementMap(ImageSize, CroppingFactor, CameraViewInfo); +} diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionModule.cpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionModule.cpp new file mode 100644 index 000000000000..4b5e499e34a9 --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionModule.cpp @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "IOpenCVLensDistortionModule.h" + +#include "Logging/LogMacros.h" + + +DEFINE_LOG_CATEGORY(LogOpenCVLensDistortion) + +////////////////////////////////////////////////////////////////////////// +// FOpenCVLensDistortionModule +class FOpenCVLensDistortionModule : public IOpenCVLensDistortionModule +{ + +}; + +////////////////////////////////////////////////////////////////////////// + +IMPLEMENT_MODULE(FOpenCVLensDistortionModule, OpenCVLensDistortion); + + diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionParameters.cpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionParameters.cpp new file mode 100644 index 000000000000..6f7c1e929657 --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Private/OpenCVLensDistortionParameters.cpp @@ -0,0 +1,145 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "OpenCVLensDistortionParameters.h" + +#include "Engine/Texture2D.h" +#include "IOpenCVLensDistortionModule.h" +#include "Logging/LogMacros.h" + + +#if WITH_OPENCV +OPENCV_INCLUDES_START +#undef check // the check macro causes problems with opencv headers +#include "opencv2/calib3d.hpp" +#include "opencv2/imgproc.hpp" +OPENCV_INCLUDES_END +#endif + + +#if WITH_OPENCV +cv::Mat FOpenCVLensDistortionParameters::ConvertToOpenCVDistortionCoefficients() const +{ + if (bUseFisheyeModel) + { + cv::Mat DistortionCoefficients(1, 4, CV_64F); + DistortionCoefficients.at(0) = K1; + DistortionCoefficients.at(1) = K2; + DistortionCoefficients.at(2) = K3; + DistortionCoefficients.at(3) = K4; + return DistortionCoefficients; + } + else + { + cv::Mat DistortionCoefficients(1, 8, CV_64F); + DistortionCoefficients.at(0) = K1; + DistortionCoefficients.at(1) = K2; + DistortionCoefficients.at(2) = P1; + DistortionCoefficients.at(3) = P2; + DistortionCoefficients.at(4) = K3; + DistortionCoefficients.at(5) = K4; + DistortionCoefficients.at(6) = K5; + DistortionCoefficients.at(7) = K6; + return DistortionCoefficients; + } +} +#endif + +#if WITH_OPENCV +cv::Mat FOpenCVLensDistortionParameters::CreateOpenCVCameraMatrix(const FVector2D& InImageSize) const +{ + cv::Mat CameraMatrix = cv::Mat::eye(3, 3, CV_64F); + CameraMatrix.at(0, 0) = F.X * InImageSize.X; + CameraMatrix.at(1, 1) = F.Y * InImageSize.Y; + CameraMatrix.at(0, 2) = C.X * InImageSize.X; + CameraMatrix.at(1, 2) = C.Y * InImageSize.Y; + return CameraMatrix; +} +#endif + +UTexture2D* FOpenCVLensDistortionParameters::CreateUndistortUVDisplacementMap(const FIntPoint& InImageSize, const float InCroppingFactor, FOpenCVCameraViewInfo& OutCameraViewInfo) const +{ +#if WITH_OPENCV + if (!IsIdentity()) + { + cv::Mat GeneratedMap1(InImageSize.X, InImageSize.Y, CV_32FC1); + cv::Mat GeneratedMap2(InImageSize.X, InImageSize.Y, CV_32FC1); + + // Use OpenCV to generate the initial direct UVmap + { + cv::Size ImageSizeCV(InImageSize.X, InImageSize.Y); + cv::Mat Identity = cv::Mat::eye(3, 3, CV_64F); + + cv::Mat CameraMatrixCV = CreateOpenCVCameraMatrix(FVector2D(InImageSize.X, InImageSize.Y)); + cv::Mat DistortionCoefficientsCV = ConvertToOpenCVDistortionCoefficients(); + + // Calculate a new camera matrix based on the camera distortion coefficients and the desired cropping factor. + //Compute the direct UVMap based on this new camera matrix. + cv::Mat NewCameraMatrix = cv::Mat::eye(3, 3, CV_64F); + if (bUseFisheyeModel) + { + cv::fisheye::estimateNewCameraMatrixForUndistortRectify(CameraMatrixCV, DistortionCoefficientsCV, ImageSizeCV, Identity, NewCameraMatrix, 1.0f - InCroppingFactor); + cv::fisheye::initUndistortRectifyMap(CameraMatrixCV, DistortionCoefficientsCV, Identity, NewCameraMatrix, ImageSizeCV, GeneratedMap1.type(), GeneratedMap1, GeneratedMap2); + } + else + { + NewCameraMatrix = getOptimalNewCameraMatrix(CameraMatrixCV, DistortionCoefficientsCV, ImageSizeCV, 1.0f - InCroppingFactor); + cv::initUndistortRectifyMap(CameraMatrixCV, DistortionCoefficientsCV, Identity, NewCameraMatrix, ImageSizeCV, GeneratedMap1.type(), GeneratedMap1, GeneratedMap2); + } + + // Estimate field of view of the undistorted image + double FocalLengthRatio, FovX, FovY, FocalLength_Unused; + cv::Point2d PrincipalPoint_Unused; + + // We pass in zero aperture size as it is unknown. (It is only required for calculating focal length and the principal point) + calibrationMatrixValues(NewCameraMatrix, ImageSizeCV, 0.0, 0.0, FovX, FovY, FocalLength_Unused, PrincipalPoint_Unused, FocalLengthRatio); + OutCameraViewInfo.HorizontalFOV = FovX; + OutCameraViewInfo.VerticalFOV = FovY; + OutCameraViewInfo.FocalLengthRatio = FocalLengthRatio; + } + + // Now convert the raw map arrays to an unreal displacement map texture + UTexture2D* Result = UTexture2D::CreateTransient(InImageSize.X, InImageSize.Y, PF_G16R16F); + + // Lock the texture so it can be modified + FTexture2DMipMap& Mip = Result->PlatformData->Mips[0]; + uint16* MipData = static_cast(Mip.BulkData.Lock(LOCK_READ_WRITE)); + check(MipData); + + // Go through each pixel and change to normalized displacement value + // OpenCV doesn't use half pixel shift coordinate but changing to displacement map fixes it. + for (int32 Y = 0; Y < InImageSize.Y; Y++) + { + uint16* Row = &MipData[Y * InImageSize.X * 2]; + for (int32 X = 0; X < InImageSize.X; X++) + { + float UOffset = GeneratedMap1.at(Y, X); + UOffset -= X; + UOffset /= static_cast(InImageSize.X); + + float VOffset = GeneratedMap2.at(Y, X); + VOffset -= Y; + VOffset /= static_cast(InImageSize.Y); + + // red channel: + Row[X * 2 + 0] = FFloat16(UOffset).Encoded; + // green channel: + Row[X * 2 + 1] = FFloat16(VOffset).Encoded; + } + } + + // Unlock the texture data + Mip.BulkData.Unlock(); + Result->UpdateResource(); + + return Result; + } + else + { + return nullptr; + } +#else + UE_LOG(LogOpenCVLensDistortion, Error, TEXT("Can't create undistortion displacement Map. OpenCV isn't enabled.")); + return nullptr; +#endif +} + diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/IOpenCVLensDistortionModule.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/IOpenCVLensDistortionModule.h new file mode 100644 index 000000000000..554f3659b018 --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/IOpenCVLensDistortionModule.h @@ -0,0 +1,44 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +#define OPENCV_LENSDISTORTION_MODULE_NAME TEXT("OpenCVLensDistortion") + +DECLARE_LOG_CATEGORY_EXTERN(LogOpenCVLensDistortion, Verbose, All); + +/** + * The public interface to this module + */ +class IOpenCVLensDistortionModule : public IModuleInterface +{ + +public: + + /** + * 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 IOpenCVLensDistortionModule& Get() + { + return FModuleManager::LoadModuleChecked(OPENCV_LENSDISTORTION_MODULE_NAME); + } + + /** + * 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(OPENCV_LENSDISTORTION_MODULE_NAME); + } +}; + +#undef OPENCV_LENSDISTORTION_MODULE_NAME diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/OpenCVLensDistortionBlueprintLibrary.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/OpenCVLensDistortionBlueprintLibrary.h new file mode 100644 index 000000000000..b1700d1d93c1 --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/OpenCVLensDistortionBlueprintLibrary.h @@ -0,0 +1,62 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "OpenCVLensDistortionParameters.h" +#include "UObject/ObjectMacros.h" + +#include "OpenCVLensDistortionBlueprintLibrary.generated.h" + + +class UTexture2D; +class UTextureRenderTarget2D; + + +UCLASS(MinimalAPI, meta=(ScriptName="OpenCVLensDistortionLibrary")) +class UOpenCVLensDistortionBlueprintLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + + + /** Draws UV displacement map within the output render target. + * - Red & green channels hold the distort to undistort displacement; + * - Blue & alpha channels hold the undistort to distort displacement. + * @param WorldContextObject Current world to get the rendering settings from (such as feature level). + * @param OutputRenderTarget The render target to draw to. Don't necessarily need to have same resolution or aspect ratio as distorted render. + * @param PreComputedUndistortDisplacementMap Distort to undistort displacement pre computed using OpenCV in engine or externally. + */ + UFUNCTION(BlueprintCallable, Category = "Lens Distortion | OpenCV", meta = (WorldContext = "WorldContextObject", ScriptMethod)) + static void DrawDisplacementMapToRenderTarget(const UObject* WorldContextObject, UTextureRenderTarget2D* OutputRenderTarget, UTexture2D* PreComputedUndistortDisplacementMap); + + /** + * Creates a texture containing a DisplacementMap in the Red and the Green channel for undistorting a camera image. + * This call can take quite some time to process depending on the resolution. + * @param LensParameters The Lens distortion parameters with which to compute the UV displacement map. + * @param ImageSize The size of the camera image to be undistorted in pixels. Scaled down resolution will have an impact. + * @param CroppingFactor One means OpenCV will attempt to crop out all empty pixels resulting from the process (essentially zooming the image). Zero will keep all pixels. + * @param CameraViewInfo Information computed by OpenCV about the undistorted space. Can be used with SceneCapture to adjust FOV. + * @return Texture2D containing the distort to undistort space displacement map. + */ + UFUNCTION(BlueprintCallable, Category = "Lens Distortion | OpenCV", meta = (WorldContext = "WorldContextObject", ScriptMethod)) + static UTexture2D* CreateUndistortUVDisplacementMap(const FOpenCVLensDistortionParameters& LensParameters, const FIntPoint& ImageSize, const float CroppingFactor, FOpenCVCameraViewInfo& CameraViewInfo); + + /* Returns true if A is equal to B (A == B) */ + UFUNCTION(BlueprintPure, meta=(DisplayName = "Equal (LensDistortionParameters)", CompactNodeTitle = "==", Keywords = "== equal", ScriptMethod), Category = "Lens Distortion") + static bool EqualEqual_CompareLensDistortionModels( + const FOpenCVLensDistortionParameters& A, + const FOpenCVLensDistortionParameters& B) + { + return A == B; + } + + /* Returns true if A is not equal to B (A != B) */ + UFUNCTION(BlueprintPure, meta = (DisplayName = "NotEqual (LensDistortionParameters)", CompactNodeTitle = "!=", Keywords = "!= not equal", ScriptMethod), Category = "Lens Distortion") + static bool NotEqual_CompareLensDistortionModels( + const FOpenCVLensDistortionParameters& A, + const FOpenCVLensDistortionParameters& B) + { + return A != B; + } +}; diff --git a/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/OpenCVLensDistortionParameters.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/OpenCVLensDistortionParameters.h new file mode 100644 index 000000000000..67b777309b63 --- /dev/null +++ b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/OpenCVLensDistortion/Public/OpenCVLensDistortionParameters.h @@ -0,0 +1,179 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_OPENCV +#include "OpenCVHelper.h" +OPENCV_INCLUDES_START +#undef check // the check macro causes problems with opencv headers +#include "opencv2/core/core.hpp" +#include "opencv2/imgcodecs.hpp" +OPENCV_INCLUDES_END +#endif + +#include "OpenCVLensDistortionParameters.generated.h" + + +class UTexture2D; +class UTextureRenderTarget2D; + + +USTRUCT(BlueprintType) +struct OPENCVLENSDISTORTION_API FOpenCVCameraViewInfo +{ + GENERATED_USTRUCT_BODY() + + FOpenCVCameraViewInfo() + : HorizontalFOV(0.0f) + , VerticalFOV(0.0f) + , FocalLengthRatio(0.0f) + { } + + /** Horizontal Field Of View in degrees */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera Info") + float HorizontalFOV; + + /** Vertical Field Of View in degrees */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera Info") + float VerticalFOV; + + /** Focal length aspect ratio -> Fy / Fx */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Camera Info") + float FocalLengthRatio; +}; + +/** + * Mathematic camera model for lens distortion/undistortion. + * Camera matrix = + * | F.X 0 C.x | + * | 0 F.Y C.Y | + * | 0 0 1 | + * where F and C are normalized. + */ +USTRUCT(BlueprintType) +struct OPENCVLENSDISTORTION_API FOpenCVLensDistortionParameters +{ + GENERATED_USTRUCT_BODY() + +public: + FOpenCVLensDistortionParameters() + : K1(0.f) + , K2(0.f) + , P1(0.f) + , P2(0.f) + , K3(0.f) + , K4(0.f) + , K5(0.f) + , K6(0.f) + , F(FVector2D(1.f, 1.f)) + , C(FVector2D(0.5f, 0.5f)) + , bUseFisheyeModel(false) + { + } + + /** Draws UV displacement map within the output render target. + * - Red & green channels hold the distort to undistort displacement; + * - Blue & alpha channels hold the undistort to distort displacement. + * @param InWorld Current world to get the rendering settings from (such as feature level). + * @param InOutputRenderTarget The render target to draw to. Don't necessarily need to have same resolution or aspect ratio as distorted render. + * @param InPreComputedUndistortDisplacementMap Distort to undistort displacement pre computed using OpenCV in engine or externally. + */ + static void DrawDisplacementMapToRenderTarget(UWorld* InWorld, UTextureRenderTarget2D* InOutputRenderTarget, UTexture2D* InPreComputedUndistortDisplacementMap); + + /** + * Creates a texture containing a DisplacementMap in the Red and the Green channel for undistorting a camera image. + * This call can take quite some time to process depending on the resolution. + * @param InImageSize The size of the camera image to be undistorted in pixels. Scaled down resolution will have an impact. + * @param InCroppingFactor One means OpenCV will attempt to crop out all empty pixels resulting from the process (essentially zooming the image). Zero will keep all pixels. + * @param OutCameraViewInfo Information computed by OpenCV about the undistorted space. Can be used with SceneCapture to adjust FOV. + */ + UTexture2D* CreateUndistortUVDisplacementMap(const FIntPoint& InImageSize, const float InCroppingFactor, FOpenCVCameraViewInfo& OutCameraViewInfo) const; + +public: + + /** Compare two lens distortion models and return whether they are equal. */ + bool operator == (const FOpenCVLensDistortionParameters& Other) const + { + return (K1 == Other.K1 && + K2 == Other.K2 && + P1 == Other.P1 && + P2 == Other.P2 && + K3 == Other.K3 && + K4 == Other.K4 && + K5 == Other.K5 && + K6 == Other.K6 && + F == Other.F && + C == Other.C && + bUseFisheyeModel == Other.bUseFisheyeModel); + } + + /** Compare two lens distortion models and return whether they are different. */ + bool operator != (const FOpenCVLensDistortionParameters& Other) const + { + return !(*this == Other); + } + + /** Returns true if lens distortion parameters are for identity lens (or default parameters) */ + bool IsIdentity() const + { + return *this == FOpenCVLensDistortionParameters(); + } + +private: + +#if WITH_OPENCV + /** Convert internal coefficients to OpenCV matrix representation */ + cv::Mat ConvertToOpenCVDistortionCoefficients() const; + + /** Convert internal normalized camera matrix to OpenCV pixel scaled matrix representation. */ + cv::Mat CreateOpenCVCameraMatrix(const FVector2D& InImageSize) const; +#endif //WITH_OPENCV + +public: + + /** Radial parameter #1. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + float K1; + + /** Radial parameter #2. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + float K2; + + /** Tangential parameter #1. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + float P1; + + /** Tangential parameter #2. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + float P2; + + /** Radial parameter #3. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + float K3; + + /** Radial parameter #4. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + float K4; + + /** Radial parameter #5. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + float K5; + + /** Radial parameter #6. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + float K6; + + /** Camera matrix's normalized Fx and Fy. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + FVector2D F; + + /** Camera matrix's normalized Cx and Cy. */ + UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + FVector2D C; + + /** Camera lens needs Fisheye camera model. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Lens Distortion|Parameters") + bool bUseFisheyeModel; +}; diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/OpenCV.Build.cs b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/OpenCV.Build.cs similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/OpenCV.Build.cs rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/OpenCV.Build.cs diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/OpenCV.tps b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/OpenCV.tps similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/OpenCV.tps rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/OpenCV.tps diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/OpenEXR.tps b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/OpenEXR.tps similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/OpenEXR.tps rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/OpenEXR.tps diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/build.bat b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/build.bat similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/build.bat rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/build.bat diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/cmake_options.txt b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/cmake_options.txt similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/cmake_options.txt rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/cmake_options.txt diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cv.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cv.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cv.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cv.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cv.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cv.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cv.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cv.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cvaux.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cvaux.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cvaux.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cvaux.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cvaux.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cvaux.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cvaux.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cvaux.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cvwimage.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cvwimage.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cvwimage.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cvwimage.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cxcore.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cxcore.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cxcore.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cxcore.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cxcore.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cxcore.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cxcore.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cxcore.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cxeigen.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cxeigen.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cxeigen.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cxeigen.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cxmisc.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cxmisc.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/cxmisc.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/cxmisc.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/highgui.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/highgui.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/highgui.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/highgui.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/ml.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/ml.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv/ml.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv/ml.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/calib3d.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/calib3d.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/calib3d.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/calib3d.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/calib3d/calib3d_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/affine.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/affine.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/affine.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/affine.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/base.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/base.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/base.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/base.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/bufferpool.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/bufferpool.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/bufferpool.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/bufferpool.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/core.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/core.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/core.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/core.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/core_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/core_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/core_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/core_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.inl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.inl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.inl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cuda.inl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_stream_accessor.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_stream_accessor.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_stream_accessor.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_stream_accessor.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_types.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_types.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_types.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cuda_types.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_dispatch.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_dispatch.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_dispatch.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_dispatch.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_helper.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_helper.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_helper.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cv_cpu_helper.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cvdef.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cvdef.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cvdef.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cvdef.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.inl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.inl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.inl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/cvstd.inl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/directx.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/directx.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/directx.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/directx.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/eigen.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/eigen.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/eigen.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/eigen.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/fast_math.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/fast_math.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/fast_math.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/fast_math.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/hal.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/hal.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/hal.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/hal.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/interface.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/interface.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/interface.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/interface.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_cpp.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_cpp.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_cpp.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_cpp.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_neon.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_neon.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_neon.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_neon.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_sse.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_sse.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_sse.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_sse.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_vsx.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_vsx.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_vsx.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/hal/intrin_vsx.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ippasync.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ippasync.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ippasync.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ippasync.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/mat.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/mat.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/mat.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/mat.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/mat.inl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/mat.inl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/mat.inl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/mat.inl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/matx.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/matx.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/matx.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/matx.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/neon_utils.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/neon_utils.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/neon_utils.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/neon_utils.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ocl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ocl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ocl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ocl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ocl_genbase.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ocl_genbase.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ocl_genbase.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ocl_genbase.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/opengl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/opengl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/opengl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/opengl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/operations.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/operations.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/operations.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/operations.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/optim.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/optim.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/optim.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/optim.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ovx.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ovx.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ovx.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ovx.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/persistence.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/persistence.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/persistence.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/persistence.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ptr.inl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ptr.inl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/ptr.inl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/ptr.inl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/saturate.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/saturate.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/saturate.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/saturate.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/softfloat.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/softfloat.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/softfloat.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/softfloat.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/sse_utils.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/sse_utils.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/sse_utils.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/sse_utils.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/traits.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/traits.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/traits.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/traits.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/types.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/types.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/types.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/types.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/types_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/types_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/types_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/types_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/utility.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/utility.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/utility.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/utility.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/utils/logger.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/utils/logger.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/utils/logger.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/utils/logger.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/utils/trace.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/utils/trace.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/utils/trace.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/utils/trace.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/va_intel.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/va_intel.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/va_intel.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/va_intel.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/version.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/version.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/version.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/version.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/vsx_utils.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/vsx_utils.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/vsx_utils.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/vsx_utils.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/wimage.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/wimage.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/core/wimage.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/core/wimage.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/cvconfig.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/cvconfig.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/cvconfig.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/cvconfig.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/all_layers.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/all_layers.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/all_layers.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/all_layers.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/dict.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/dict.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/dict.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/dict.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.inl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.inl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.inl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/dnn.inl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.details.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.details.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.details.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.details.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/layer.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/shape_utils.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/shape_utils.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/dnn/shape_utils.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/dnn/shape_utils.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/features2d.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/features2d.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/features2d.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/features2d.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/features2d/features2d.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/features2d/features2d.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/features2d/features2d.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/features2d/features2d.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/all_indices.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/all_indices.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/all_indices.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/all_indices.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/allocator.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/allocator.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/allocator.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/allocator.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/any.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/any.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/any.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/any.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/autotuned_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/autotuned_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/autotuned_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/autotuned_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/composite_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/composite_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/composite_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/composite_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/config.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/config.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/config.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/config.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/defines.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/defines.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/defines.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/defines.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/dist.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/dist.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/dist.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/dist.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/dummy.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/dummy.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/dummy.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/dummy.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/dynamic_bitset.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/dynamic_bitset.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/dynamic_bitset.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/dynamic_bitset.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/flann.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/flann.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/flann.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/flann.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/flann_base.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/flann_base.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/flann_base.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/flann_base.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/general.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/general.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/general.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/general.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/ground_truth.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/ground_truth.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/ground_truth.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/ground_truth.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/hdf5.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/hdf5.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/hdf5.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/hdf5.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/heap.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/heap.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/heap.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/heap.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/hierarchical_clustering_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/hierarchical_clustering_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/hierarchical_clustering_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/hierarchical_clustering_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/index_testing.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/index_testing.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/index_testing.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/index_testing.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_single_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_single_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_single_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/kdtree_single_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/kmeans_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/kmeans_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/kmeans_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/kmeans_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/linear_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/linear_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/linear_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/linear_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/logger.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/logger.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/logger.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/logger.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_table.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_table.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_table.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/lsh_table.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/matrix.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/matrix.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/matrix.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/matrix.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/miniflann.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/miniflann.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/miniflann.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/miniflann.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/nn_index.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/nn_index.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/nn_index.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/nn_index.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/object_factory.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/object_factory.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/object_factory.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/object_factory.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/params.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/params.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/params.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/params.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/random.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/random.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/random.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/random.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/result_set.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/result_set.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/result_set.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/result_set.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/sampling.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/sampling.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/sampling.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/sampling.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/saving.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/saving.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/saving.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/saving.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/simplex_downhill.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/simplex_downhill.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/simplex_downhill.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/simplex_downhill.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/timer.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/timer.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/flann/timer.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/flann/timer.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/highgui.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/highgui.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/highgui.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/highgui.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/highgui/highgui_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/imgcodecs_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/ios.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/ios.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/ios.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgcodecs/ios.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/detail/distortion_model.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/detail/distortion_model.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/detail/distortion_model.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/detail/distortion_model.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/hal.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/hal.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/hal.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/hal.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/interface.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/interface.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/interface.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/hal/interface.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/imgproc_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/types_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/types_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/imgproc/types_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/imgproc/types_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/ml.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/ml.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/ml.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/ml.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/ml/ml.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/ml/ml.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/ml/ml.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/ml/ml.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/objdetect.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/objdetect.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/objdetect.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/objdetect.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/objdetect/detection_based_tracker.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/objdetect/detection_based_tracker.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/objdetect/detection_based_tracker.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/objdetect/detection_based_tracker.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/objdetect/objdetect_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/opencv.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/opencv.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/opencv.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/opencv.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/opencv_modules.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/opencv_modules.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/opencv_modules.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/opencv_modules.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/photo.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/photo.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/photo.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/photo.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/photo/cuda.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/photo/cuda.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/photo/cuda.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/photo/cuda.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/photo/photo.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/photo/photo.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/photo/photo.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/photo/photo.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/photo/photo_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/photo/photo_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/photo/photo_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/photo/photo_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/emdL1.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/emdL1.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/emdL1.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/emdL1.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/hist_cost.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/hist_cost.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/hist_cost.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/hist_cost.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/shape.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/shape.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/shape.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/shape.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_distance.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_distance.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_distance.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_distance.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_transformer.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_transformer.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_transformer.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/shape/shape_transformer.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/autocalib.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/autocalib.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/autocalib.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/autocalib.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/blenders.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/blenders.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/blenders.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/blenders.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/camera.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/camera.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/camera.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/camera.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/exposure_compensate.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/exposure_compensate.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/exposure_compensate.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/exposure_compensate.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/matchers.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/matchers.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/matchers.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/matchers.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/motion_estimators.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/motion_estimators.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/motion_estimators.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/motion_estimators.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/seam_finders.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/seam_finders.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/seam_finders.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/seam_finders.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/timelapsers.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/timelapsers.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/timelapsers.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/timelapsers.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util_inl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util_inl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util_inl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/util_inl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers_inl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers_inl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers_inl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/detail/warpers_inl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/warpers.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/warpers.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/stitching/warpers.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/stitching/warpers.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/superres.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/superres.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/superres.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/superres.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/superres/optical_flow.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/superres/optical_flow.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/superres/optical_flow.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/superres/optical_flow.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video/background_segm.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video/background_segm.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video/background_segm.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video/background_segm.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video/tracking.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video/tracking.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video/tracking.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video/tracking.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video/tracking_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video/tracking_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video/tracking_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video/tracking_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video/video.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video/video.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/video/video.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/video/video.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videoio.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videoio.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videoio.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videoio.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videoio/cap_ios.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videoio/cap_ios.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videoio/cap_ios.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videoio/cap_ios.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio_c.h b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio_c.h similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio_c.h rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videoio/videoio_c.h diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/deblurring.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/deblurring.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/deblurring.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/deblurring.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching_inl.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching_inl.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching_inl.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/fast_marching_inl.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/frame_source.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/frame_source.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/frame_source.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/frame_source.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/global_motion.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/global_motion.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/global_motion.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/global_motion.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/inpainting.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/inpainting.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/inpainting.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/inpainting.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/log.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/log.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/log.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/log.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_core.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_core.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_core.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_core.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_stabilizing.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_stabilizing.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_stabilizing.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/motion_stabilizing.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/optical_flow.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/optical_flow.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/optical_flow.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/optical_flow.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/outlier_rejection.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/outlier_rejection.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/outlier_rejection.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/outlier_rejection.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/ring_buffer.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/ring_buffer.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/ring_buffer.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/ring_buffer.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/stabilizer.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/stabilizer.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/stabilizer.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/stabilizer.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/wobble_suppression.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/wobble_suppression.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/videostab/wobble_suppression.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/videostab/wobble_suppression.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/world.hpp b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/world.hpp similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/include/opencv2/world.hpp rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/include/opencv2/world.hpp diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/libPNG.tps b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/libPNG.tps similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/libPNG.tps rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/libPNG.tps diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/libTiff.tps b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/libTiff.tps similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/libTiff.tps rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/libTiff.tps diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/zlib.tps b/Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/zlib.tps similarity index 100% rename from Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/ThirdParty/OpenCV/zlib.tps rename to Engine/Plugins/Compositing/OpenCVLensDistortion/Source/ThirdParty/OpenCV/zlib.tps diff --git a/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Private/EditorScriptingUtils.cpp b/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Private/EditorScriptingUtils.cpp index e1ba4faceff6..94413fca89eb 100644 --- a/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Private/EditorScriptingUtils.cpp +++ b/Engine/Plugins/Editor/EditorScriptingUtilities/Source/EditorScriptingUtilities/Private/EditorScriptingUtils.cpp @@ -139,7 +139,7 @@ namespace EditorScriptingUtils int32 FoundIndex = 0; AnyAssetPath.FindChar(TEXT(' '), FoundIndex); check(FoundIndex > INDEX_NONE && FoundIndex < AnyAssetPath.Len()); // because of TrimStartAndEnd - + // Confirm that it's a valid Class FString ClassName = AnyAssetPath.Left(FoundIndex); @@ -150,7 +150,7 @@ namespace EditorScriptingUtils const int32 StrLen = FCString::Strlen(INVALID_OBJECTNAME_CHARACTERS); for (int32 Index = 0; Index < StrLen; ++Index) { - int32 InvalidFoundIndex = 0; + int32 InvalidFoundIndex = 0; if (ClassName.FindChar(INVALID_OBJECTNAME_CHARACTERS[Index], InvalidFoundIndex)) { OutFailureReason = FString::Printf(TEXT("Can't convert the path %s because it contains invalid characters (probably spaces)."), *AnyAssetPath); @@ -191,30 +191,28 @@ namespace EditorScriptingUtils return FString(); } - // Get the Short name of the asset. "MyAsset.MyAsset:InnerAsset.2ndInnerAsset" from "/Game/Folder/MyAsset.MyAsset:InnerAsset.2ndInnerAsset" - FString ObjectName; // = FPackageName::GetShortName(TextPath); - { - int32 IndexOfLastSlash = INDEX_NONE; - TextPath.FindLastChar(TEXT('/'), IndexOfLastSlash); - ObjectName = TextPath.Mid(IndexOfLastSlash + 1); - TextPath = TextPath.Left(IndexOfLastSlash); - } + // Get asset full name, i.e."PackageName.ObjectName:InnerAssetName.2ndInnerAssetName" from "/Game/Folder/PackageName.ObjectName:InnerAssetName.2ndInnerAssetName" + FString AssetFullName = FPackageName::GetShortName(TextPath); - // Get the first Object name. "MyAsset" from "MyAsset.MyAsset:InnerAsset.2ndInnerAsset" - //ObjectName = FPackageName::ObjectPathToObjectName(ObjectName); won't works because of the possible ':' character + // Remove possible ':' character from asset full name { - // Check for a top level object - int32 ObjectDelimiterIdx; - if (ObjectName.FindChar(TEXT('.'), ObjectDelimiterIdx)) + int32 IndexOfSemiColumn; + if (AssetFullName.FindChar(TEXT(':'), IndexOfSemiColumn)) { - ObjectName = ObjectName.Left(ObjectDelimiterIdx); + AssetFullName = AssetFullName.Left(IndexOfSemiColumn); } } + // Get the object name + FString ObjectName = FPackageName::ObjectPathToObjectName(AssetFullName); if (ObjectName.IsEmpty()) { - OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it doesn't contain an asset name."), *AnyAssetPath); - return FString(); + ObjectName = FPackageName::ObjectPathToPackageName(AssetFullName); + if (ObjectName.IsEmpty()) + { + OutFailureReason = FString::Printf(TEXT("Can't convert the path '%s' because it doesn't contain an asset name."), *AnyAssetPath); + return FString(); + } } // Confirm that we have a valid Root Package and get the valid PackagePath /Game/MyFolder/MyAsset @@ -236,7 +234,7 @@ namespace EditorScriptingUtils return FString(); } - FString ObjectPath = FString::Printf(TEXT("%s/%s.%s"), *PackagePath, *ObjectName, *ObjectName); + FString ObjectPath = FString::Printf(TEXT("%s.%s"), *PackagePath, *ObjectName); if (FPackageName::IsScriptPackage(ObjectPath)) { diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorCommands.cpp b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorCommands.cpp index aeadb448fec8..bb592d6d9f13 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorCommands.cpp +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorCommands.cpp @@ -47,6 +47,13 @@ FUIAction UMeshEditorInstantCommand::MakeUIAction( IMeshEditorModeUIContract& Me FCanExecuteAction::CreateLambda( [&MeshEditorMode] { return MeshEditorMode.GetSelectedEditableMeshes().Num() > 0; } ) ); } + else if( ElementType == EEditableMeshElementType::Any ) + { + UIAction = FUIAction( + ExecuteAction, + FCanExecuteAction::CreateLambda( [&MeshEditorMode] { return MeshEditorMode.GetSelectedMeshElementType() != EEditableMeshElementType::Invalid; } ) + ); + } else { UIAction = FUIAction( @@ -125,7 +132,13 @@ FMeshEditorAnyElementCommands::FMeshEditorAnyElementCommands() void FMeshEditorAnyElementCommands::RegisterCommands() { - UI_COMMAND(DeleteMeshElement, "Delete", "Delete selected mesh elements, including polygons partly defined by selected elements.", EUserInterfaceActionType::Button, FInputChord(EKeys::Delete)); + for( UMeshEditorCommand* Command : MeshEditorCommands::Get() ) + { + if( Command->GetElementType() == EEditableMeshElementType::Any ) + { + Command->RegisterUICommand( this ); + } + } } FMeshEditorVertexCommands::FMeshEditorVertexCommands() @@ -191,9 +204,7 @@ FMeshEditorPolygonCommands::FMeshEditorPolygonCommands() void FMeshEditorPolygonCommands::RegisterCommands() { UI_COMMAND(MovePolygon, "Move", "Move selected polygons using a transform gizmo, or click and drag to move polygons directly.", EUserInterfaceActionType::RadioButton, FInputChord()); - UI_COMMAND(FlipPolygon, "Flip", "Flip the currently selected polygons.", EUserInterfaceActionType::Button, FInputChord(EKeys::F, EModifierKey::Shift)); UI_COMMAND(TriangulatePolygon, "Triangulate", "Triangulate the currently selected polygons.", EUserInterfaceActionType::Button, FInputChord(EKeys::T)); - UI_COMMAND(AssignMaterial, "Assign Material", "Assigns the highlighted material in the Content Browser to the currently selected polygons.", EUserInterfaceActionType::Button, FInputChord(EKeys::M)); for( UMeshEditorCommand* Command : MeshEditorCommands::Get() ) { diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.cpp b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.cpp index f53a361f8d61..226e6a0ff917 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.cpp +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.cpp @@ -4,6 +4,7 @@ #include "MeshEditorCommands.h" #include "MeshEditorStyle.h" #include "MeshEditorModeToolkit.h" +#include "MeshEditorUtilities.h" #include "EditableMesh.h" #include "MeshAttributes.h" #include "EditableMeshFactory.h" @@ -23,7 +24,6 @@ #include "ActorViewportTransformable.h" #include "ViewportWorldInteraction.h" #include "IViewportInteractionModule.h" -#include "IContentBrowserSingleton.h" #include "VIBaseTransformGizmo.h" // For EGizmoHandleTypes #include "Engine/Selection.h" #include "EngineUtils.h" @@ -47,6 +47,8 @@ #include "Components/PrimitiveComponent.h" #include "ILevelViewport.h" #include "SLevelViewport.h" +#include "MeshEditorSelectionModifiers.h" +#include "Algo/Find.h" #define LOCTEXT_NAMESPACE "MeshEditorMode" @@ -155,7 +157,7 @@ TUniquePtr FMeshEditorMode::FSelectOrDeselectMeshElementsChange::Execut const UEditableMesh* EditableMesh = MeshEditorMode.FindEditableMesh( *MeshElementToSelect.Component, MeshElementToSelect.ElementAddress.SubMeshAddress ); if( EditableMesh != nullptr ) { - if( IsElementIDValid( MeshElementToSelect, EditableMesh ) ) + if( MeshElementToSelect.IsElementIDValid( EditableMesh ) && MeshEditorMode.GetSelectedMeshElementIndex( MeshElementToSelect ) == INDEX_NONE ) { FMeshElement& AddedSelectedMeshElement = MeshEditorMode.SelectedMeshElements[ MeshEditorMode.SelectedMeshElements.Add( MeshElementToSelect ) ]; AddedSelectedMeshElement.LastSelectTime = CurrentRealTime; @@ -405,6 +407,9 @@ FMeshEditorMode::FMeshEditorMode() EquippedEdgeAction( EMeshEditAction::Move ), EquippedPolygonAction( EMeshEditAction::Move ), ActiveAction( NAME_None ), + EquippedVertexSelectionModifier( NAME_None ), + EquippedEdgeSelectionModifier( NAME_None ), + EquippedPolygonSelectionModifier( NAME_None ), bIsCapturingUndoForPreview( false ), PreviewRevertChanges(), ActiveActionModifiedMeshes(), @@ -450,6 +455,7 @@ FMeshEditorMode::FMeshEditorMode() FMeshEditorVertexCommands::Register(); FMeshEditorEdgeCommands::Register(); FMeshEditorPolygonCommands::Register(); + FMeshEditorSelectionModifiers::Register(); // Register UI commands BindCommands(); @@ -467,6 +473,7 @@ void FMeshEditorMode::Initialize() FMeshEditorMode::~FMeshEditorMode() { // Unregister mesh editor actions + FMeshEditorSelectionModifiers::Unregister(); FMeshEditorPolygonCommands::Unregister(); FMeshEditorEdgeCommands::Unregister(); FMeshEditorVertexCommands::Unregister(); @@ -637,7 +644,6 @@ void FMeshEditorMode::PlayFinishActionSound( FName NewAction, UViewportInteracto void FMeshEditorMode::BindCommands() { const FMeshEditorCommonCommands& MeshEditorCommonCommands( FMeshEditorCommonCommands::Get() ); - const FMeshEditorAnyElementCommands& MeshEditorAnyElementCommands( FMeshEditorAnyElementCommands::Get() ); const FMeshEditorVertexCommands& MeshEditorVertexCommands( FMeshEditorVertexCommands::Get() ); const FMeshEditorEdgeCommands& MeshEditorEdgeCommands( FMeshEditorEdgeCommands::Get() ); const FMeshEditorPolygonCommands& MeshEditorPolygonCommands( FMeshEditorPolygonCommands::Get() ); @@ -650,9 +656,6 @@ void FMeshEditorMode::BindCommands() RegisterPolygonEditingMode( MeshEditorPolygonCommands.MovePolygon, EMeshEditAction::Move ); RegisterCommonEditingMode( MeshEditorCommonCommands.DrawVertices, EMeshEditAction::DrawVertices ); - // Register commands which work regardless of which element type is selected - RegisterAnyElementCommand( MeshEditorAnyElementCommands.DeleteMeshElement, FExecuteAction::CreateLambda( [this] { DeleteSelectedMeshElement(); } ) ); - // Register commands which work even without a selected element, as long as at least one mesh is selected #if EDITABLE_MESH_USE_OPENSUBDIV RegisterCommonCommand( MeshEditorCommonCommands.AddSubdivisionLevel, FExecuteAction::CreateLambda( [this] { AddOrRemoveSubdivisionLevel( true ); } ) ); @@ -695,9 +698,7 @@ void FMeshEditorMode::BindCommands() RegisterEdgeCommand( MeshEditorEdgeCommands.SelectEdgeLoop, FExecuteAction::CreateLambda( [ this ] { SelectEdgeLoops(); } ) ); - RegisterPolygonCommand( MeshEditorPolygonCommands.FlipPolygon, FExecuteAction::CreateLambda( [this] { FlipSelectedPolygons(); } ) ); RegisterPolygonCommand( MeshEditorPolygonCommands.TriangulatePolygon, FExecuteAction::CreateLambda( [this] { TriangulateSelectedPolygons(); } ) ); - RegisterPolygonCommand( MeshEditorPolygonCommands.AssignMaterial, FExecuteAction::CreateLambda( [this] { AssignSelectedMaterialToSelectedPolygons(); } ) ); for( UMeshEditorCommand* Command : MeshEditorCommands::Get() ) { @@ -722,7 +723,11 @@ void FMeshEditorMode::BindCommands() case EEditableMeshElementType::Polygon: PolygonActions.Emplace( Command->GetUICommandInfo(), Command->MakeUIAction( *this ) ); break; - + case EEditableMeshElementType::Any: + VertexActions.Emplace( Command->GetUICommandInfo(), Command->MakeUIAction( *this ) ); + EdgeActions.Emplace( Command->GetUICommandInfo(), Command->MakeUIAction( *this ) ); + PolygonActions.Emplace( Command->GetUICommandInfo(), Command->MakeUIAction( *this ) ); + break; default: check( 0 ); } @@ -757,6 +762,8 @@ void FMeshEditorMode::BindCommands() { PolygonCommands->MapAction( PolygonAction.Get<0>(), PolygonAction.Get<1>() ); } + + BindSelectionModifiersCommands(); } @@ -1285,7 +1292,7 @@ void FMeshEditorMode::Tick( FEditorViewportClient* ViewportClient, float DeltaTi for( FMeshElement& FadingOutHoveredMeshElement : FadingOutHoveredMeshElements ) { const UEditableMesh* EditableMesh = FindEditableMesh( *FadingOutHoveredMeshElement.Component.Get(), FadingOutHoveredMeshElement.ElementAddress.SubMeshAddress ); - if( IsElementIDValid( FadingOutHoveredMeshElement, EditableMesh ) ) + if( FadingOutHoveredMeshElement.IsElementIDValid( EditableMesh ) ) { const float TimeSinceLastHovered = CurrentRealTime - FadingOutHoveredMeshElement.LastHoverTime; float Opacity = 1.0f - ( TimeSinceLastHovered / HoverFadeTime ); @@ -1790,90 +1797,98 @@ void FMeshEditorMode::DeselectMeshElements( const TMap> MeshesWithElementsToDelete; - GetSelectedMeshesAndElements( EEditableMeshElementType::Any, MeshesWithElementsToDelete ); - if( MeshesWithElementsToDelete.Num() == 0 ) - { - // @todo should this count as a failure case? - return false; - } - - FScopedTransaction Transaction( LOCTEXT( "UndoDeleteMeshElement", "Delete" ) ); - - CommitSelectedMeshes(); - - // Refresh selection (committing may have created a new mesh instance) - GetSelectedMeshesAndElements( EEditableMeshElementType::Any, MeshesWithElementsToDelete ); - - // Deselect the mesh elements before we delete them. This will make sure they become selected again after undo. - DeselectMeshElements( MeshesWithElementsToDelete ); - - for( const auto& MeshAndElements : MeshesWithElementsToDelete ) - { - UEditableMesh* EditableMesh = MeshAndElements.Key; - - EditableMesh->StartModification( EMeshModificationType::Final, EMeshTopologyChange::TopologyChange ); - - for( const FMeshElement& MeshElementToDelete : MeshAndElements.Value ) - { - const bool bDeleteOrphanedEdges = true; - const bool bDeleteOrphanedVertices = true; - const bool bDeleteOrphanedVertexInstances = true; - const bool bDeleteEmptySections = true; - - // If we deleted the same polygon on multiple selected instances of the same mesh, the polygon could already have been deleted - // by the time we get here - if( IsElementIDValid( MeshElementToDelete, EditableMesh ) ) + FUIAction SelectionModifierUIAction = FUIAction( + FExecuteAction::CreateLambda( [ SelectionModifier, MeshEditorMode = this] { MeshEditorMode->SetEquippedSelectionModifier( MeshEditorMode->GetMeshElementSelectionMode(), SelectionModifier->GetSelectionModifierName() ); } ), + FCanExecuteAction::CreateLambda( [ SelectionModifier, MeshEditorMode = this ] { - if( MeshElementToDelete.ElementAddress.ElementType == EEditableMeshElementType::Vertex ) - { - EditableMesh->DeleteVertexAndConnectedEdgesAndPolygons( - FVertexID( MeshElementToDelete.ElementAddress.ElementID ), - bDeleteOrphanedEdges, - bDeleteOrphanedVertices, - bDeleteOrphanedVertexInstances, - bDeleteEmptySections ); + return MeshEditorMode->IsMeshElementTypeSelectedOrIsActiveSelectionMode( SelectionModifier->GetElementType() ) || SelectionModifier->GetElementType() == EEditableMeshElementType::Any; + } ), + FIsActionChecked::CreateLambda( [ SelectionModifier, MeshEditorMode = this ] { return SelectionModifier == MeshEditorMode->GetEquippedSelectionModifier(); } ) ); - } - else if( MeshElementToDelete.ElementAddress.ElementType == EEditableMeshElementType::Edge ) - { - EditableMesh->DeleteEdgeAndConnectedPolygons( - FEdgeID( MeshElementToDelete.ElementAddress.ElementID ), - bDeleteOrphanedEdges, - bDeleteOrphanedVertices, - bDeleteOrphanedVertexInstances, - bDeleteEmptySections ); - } - else if( MeshElementToDelete.ElementAddress.ElementType == EEditableMeshElementType::Polygon ) - { - static TArray PolygonIDsToDelete; - PolygonIDsToDelete.Reset(); - PolygonIDsToDelete.Add( FPolygonID( MeshElementToDelete.ElementAddress.ElementID ) ); - EditableMesh->DeletePolygons( - PolygonIDsToDelete, - bDeleteOrphanedEdges, - bDeleteOrphanedVertices, - bDeleteOrphanedVertexInstances, - bDeleteEmptySections ); - } + switch ( SelectionModifier->GetElementType() ) + { + case EEditableMeshElementType::Invalid: + break; + case EEditableMeshElementType::Vertex: + VertexSelectionModifiersActions.Emplace( SelectionModifier->GetUICommandInfo(), SelectionModifierUIAction ); + + if ( EquippedVertexSelectionModifier == NAME_None ) + { + EquippedVertexSelectionModifier = SelectionModifier->GetSelectionModifierName(); } + break; + case EEditableMeshElementType::Edge: + EdgeSelectionModifiersActions.Emplace( SelectionModifier->GetUICommandInfo(), SelectionModifierUIAction ); + + if ( EquippedEdgeSelectionModifier == NAME_None ) + { + EquippedEdgeSelectionModifier = SelectionModifier->GetSelectionModifierName(); + } + break; + case EEditableMeshElementType::Polygon: + PolygonSelectionModifiersActions.Emplace( SelectionModifier->GetUICommandInfo(), SelectionModifierUIAction ); + + if ( EquippedPolygonSelectionModifier == NAME_None ) + { + EquippedPolygonSelectionModifier = SelectionModifier->GetSelectionModifierName(); + } + break; + case EEditableMeshElementType::Any: + VertexSelectionModifiersActions.Emplace( SelectionModifier->GetUICommandInfo(), SelectionModifierUIAction ); + EdgeSelectionModifiersActions.Emplace( SelectionModifier->GetUICommandInfo(), SelectionModifierUIAction ); + PolygonSelectionModifiersActions.Emplace( SelectionModifier->GetUICommandInfo(), SelectionModifierUIAction ); + + if ( EquippedVertexSelectionModifier == NAME_None ) + { + EquippedVertexSelectionModifier = SelectionModifier->GetSelectionModifierName(); + } + + if ( EquippedEdgeSelectionModifier == NAME_None ) + { + EquippedEdgeSelectionModifier = SelectionModifier->GetSelectionModifierName(); + } + + if ( EquippedPolygonSelectionModifier == NAME_None ) + { + EquippedPolygonSelectionModifier = SelectionModifier->GetSelectionModifierName(); + } + break; + default: + check( false ); } - - EditableMesh->EndModification(); - - TrackUndo( EditableMesh, EditableMesh->MakeUndo() ); } - - return true; } +void FMeshEditorMode::ModifySelection( TArray< FMeshElement >& InOutMeshElementsToSelect ) +{ + UMeshEditorSelectionModifier* SelectionModifier = GetEquippedSelectionModifier(); + + if ( !SelectionModifier ) + { + return; + } + + TMap< UEditableMesh*, TArray< FMeshElement > > EditableMeshesAndPolygons; + for ( FMeshElement& MeshElement : InOutMeshElementsToSelect ) + { + EditableMeshesAndPolygons.Add( FindOrCreateEditableMesh( *MeshElement.Component, MeshElement.ElementAddress.SubMeshAddress ) ).Add( MeshElement ); + } + + SelectionModifier->ModifySelection( EditableMeshesAndPolygons ); + InOutMeshElementsToSelect.Reset(); + + for ( TPair< UEditableMesh*, TArray< FMeshElement > >& MeshElements : EditableMeshesAndPolygons ) + { + for ( FMeshElement& MeshElement : MeshElements.Value ) + { + InOutMeshElementsToSelect.Add( MeshElement ); + } + } +} #if EDITABLE_MESH_USE_OPENSUBDIV void FMeshEditorMode::AddOrRemoveSubdivisionLevel( const bool bShouldAdd ) @@ -2153,56 +2168,6 @@ bool FMeshEditorMode::WeldSelectedVertices() } -bool FMeshEditorMode::FlipSelectedPolygons() -{ - if( ActiveAction != NAME_None ) - { - return false; - } - - static TMap< UEditableMesh*, TArray > MeshesAndPolygons; - GetSelectedMeshesAndPolygons( /* Out */ MeshesAndPolygons ); - - if( MeshesAndPolygons.Num() == 0 ) - { - // @todo should this count as a failure case? - return false; - } - - const FScopedTransaction Transaction( LOCTEXT( "UndoFlipPolygon", "Flip Polygon" ) ); - - CommitSelectedMeshes(); - - // Refresh selection (committing may have created a new mesh instance) - GetSelectedMeshesAndPolygons( /* Out */ MeshesAndPolygons ); - - // Flip selected polygons - for( const auto& MeshAndPolygons : MeshesAndPolygons ) - { - UEditableMesh* EditableMesh = MeshAndPolygons.Key; - - EditableMesh->StartModification( EMeshModificationType::Final, EMeshTopologyChange::TopologyChange ); - - static TArray PolygonsToFlip; - PolygonsToFlip.Reset(); - - for( const FMeshElement& PolygonElement : MeshAndPolygons.Value ) - { - const FPolygonID PolygonID( PolygonElement.ElementAddress.ElementID ); - PolygonsToFlip.Add( PolygonID ); - } - - EditableMesh->FlipPolygons( PolygonsToFlip ); - - EditableMesh->EndModification(); - - TrackUndo( EditableMesh, EditableMesh->MakeUndo() ); - } - - return true; -} - - bool FMeshEditorMode::TriangulateSelectedPolygons() { if( ActiveAction != NAME_None ) @@ -2287,19 +2252,6 @@ bool FMeshEditorMode::TriangulateSelectedPolygons() } -bool FMeshEditorMode::AssignSelectedMaterialToSelectedPolygons() -{ - IContentBrowserSingleton& ContentBrowser = FModuleManager::LoadModuleChecked( "ContentBrowser" ).Get(); - - static TArray SelectedAssets; - ContentBrowser.GetSelectedAssets( SelectedAssets ); - - UMaterialInterface* SelectedMaterial = FAssetData::GetFirstAsset( SelectedAssets ); - - return AssignMaterialToSelectedPolygons( SelectedMaterial ); -} - - bool FMeshEditorMode::AssignMaterialToSelectedPolygons( UMaterialInterface* SelectedMaterial ) { if( SelectedMaterial ) @@ -2328,93 +2280,10 @@ bool FMeshEditorMode::AssignMaterialToSelectedPolygons( UMaterialInterface* Sele { UEditableMesh* EditableMesh = MeshAndPolygons.Key; - EditableMesh->StartModification( EMeshModificationType::Final, EMeshTopologyChange::TopologyChange ); - { - const UMeshDescription* MeshDescription = EditableMesh->GetMeshDescription(); - - UPrimitiveComponent* Component = nullptr; - for( const FMeshElement& PolygonElement : MeshAndPolygons.Value ) - { - Component = PolygonElement.Component.Get(); - break; - } - check( Component != nullptr ); - - // See if there's a polygon group using this material, and if not create one. - // @todo mesheditor: This currently imposes the limitation that each polygon group has a unique material. - // Eventually we will need to be able to specify PolygonGroup properties in the editor, and ask the user for - // further details if there is more than one polygon group which matches the material. - FPolygonGroupID PolygonGroupToAssign = FPolygonGroupID::Invalid; - - const TPolygonGroupAttributeArray& PolygonGroupMaterialAssetNames = MeshDescription->PolygonGroupAttributes().GetAttributes( MeshAttribute::PolygonGroup::MaterialAssetName ); - - for( const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs() ) - { - const FName PolygonGroupMaterialName = PolygonGroupMaterialAssetNames[ PolygonGroupID ]; - if( SelectedMaterial->GetPathName() == PolygonGroupMaterialName.ToString() ) - { - // We only expect to find one polygon group containing this material at the moment. - // We need to provide a way of distinguishing different polygon groups with the same material. - ensure( PolygonGroupToAssign == FPolygonGroupID::Invalid ); - PolygonGroupToAssign = PolygonGroupID; - } - } - - // If we didn't find the material being used anywhere, create a new polygon group - if( PolygonGroupToAssign == FPolygonGroupID::Invalid ) - { - // Helper function which returns a unique FName for the material slot name, based on the material's asset name, - // and adding a unique suffix if there are other polygon groups with the same material slot name. - auto MakeUniqueSlotName = [ MeshDescription ]( FName Name ) -> FName - { - const TPolygonGroupAttributeArray& MaterialSlotNames = MeshDescription->PolygonGroupAttributes().GetAttributes( MeshAttribute::PolygonGroup::MaterialAssetName ); - for( const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs() ) - { - const FName ExistingName = MaterialSlotNames[ PolygonGroupID ]; - if( ExistingName.GetComparisonIndex() == Name.GetComparisonIndex() ) - { - Name = FName( Name, FMath::Max( Name.GetNumber(), ExistingName.GetNumber() + 1 ) ); - } - } - return Name; - }; - - const FName UniqueSlotName = MakeUniqueSlotName( SelectedMaterial->GetFName() ); - - static TArray PolygonGroupsToCreate; - PolygonGroupsToCreate.Reset( 1 ); - PolygonGroupsToCreate.Emplace(); - - FPolygonGroupToCreate& PolygonGroupToCreate = PolygonGroupsToCreate.Last(); - PolygonGroupToCreate.PolygonGroupAttributes.Attributes.Emplace( MeshAttribute::PolygonGroup::MaterialAssetName, 0, FMeshElementAttributeValue( FName( *SelectedMaterial->GetPathName() ) ) ); - PolygonGroupToCreate.PolygonGroupAttributes.Attributes.Emplace( MeshAttribute::PolygonGroup::ImportedMaterialSlotName, 0, FMeshElementAttributeValue( UniqueSlotName )); - static TArray NewPolygonGroupIDs; - EditableMesh->CreatePolygonGroups( PolygonGroupsToCreate, NewPolygonGroupIDs ); - PolygonGroupToAssign = NewPolygonGroupIDs[ 0 ]; - } - - static TArray PolygonsToAssign; - PolygonsToAssign.Reset(); - - for( const FMeshElement& PolygonElement : MeshAndPolygons.Value ) - { - const FPolygonID PolygonID( PolygonElement.ElementAddress.ElementID ); - - PolygonsToAssign.Emplace(); - FPolygonGroupForPolygon& PolygonGroupForPolygon = PolygonsToAssign.Last(); - PolygonGroupForPolygon.PolygonID = PolygonID; - PolygonGroupForPolygon.PolygonGroupID = PolygonGroupToAssign; - } - - const bool bDeleteOrphanedPolygonGroups = true; - EditableMesh->AssignPolygonsToPolygonGroups( PolygonsToAssign, bDeleteOrphanedPolygonGroups ); - } - EditableMesh->EndModification(); + FMeshEditorUtilities::AssignMaterialToPolygons( SelectedMaterial, EditableMesh, MeshAndPolygons.Value ); TrackUndo( EditableMesh, EditableMesh->MakeUndo() ); } - - return true; } return true; @@ -2490,7 +2359,7 @@ void FMeshEditorMode::AddMeshElementToOverlay( UOverlayComponent* OverlayCompone UEditableMesh* EditableMesh = FindOrCreateEditableMesh( *MeshElement.Component, MeshElement.ElementAddress.SubMeshAddress ); if( EditableMesh != nullptr ) { - if( IsElementIDValid( MeshElement, EditableMesh ) ) + if( MeshElement.IsElementIDValid( EditableMesh ) ) { const UMeshDescription* MeshDescription = EditableMesh->GetMeshDescription(); const TVertexAttributeArray& VertexPositions = MeshDescription->VertexAttributes().GetAttributes( MeshAttribute::Vertex::Position ); @@ -4153,6 +4022,8 @@ void FMeshEditorMode::OnViewportInteractionInputAction( FEditorViewportClient& V FSelectOrDeselectMeshElementsChangeInput ChangeInput; ChangeInput.MeshElementsToDeselect.Add( SelectedMeshElements[ AlreadySelectedMeshElement ] ); + ModifySelection( ChangeInput.MeshElementsToDeselect ); + TrackUndo( MeshEditorModeProxyObject, FSelectOrDeselectMeshElementsChange( MoveTemp( ChangeInput ) ).Execute( MeshEditorModeProxyObject ) ); } else if( MeshElementSelectionMode == EEditableMeshElementType::Any || MeshElementSelectionMode == HoveredMeshElement.ElementAddress.ElementType ) @@ -4176,6 +4047,7 @@ void FMeshEditorMode::OnViewportInteractionInputAction( FEditorViewportClient& V // Select the element under the mouse cursor ChangeInput.MeshElementsToSelect.Add( HoveredMeshElement ); + ModifySelection( ChangeInput.MeshElementsToSelect ); TUniquePtr RevertChange = FSelectOrDeselectMeshElementsChange( MoveTemp( ChangeInput ) ).Execute( MeshEditorModeProxyObject ); @@ -4686,7 +4558,7 @@ void FMeshEditorMode::RefreshTransformables( const bool bNewObjectsSelected ) UEditableMesh* EditableMesh = FindOrCreateEditableMesh( *MeshElement.Component, MeshElement.ElementAddress.SubMeshAddress ); if( EditableMesh != nullptr ) { - if( IsElementIDValid( MeshElement, EditableMesh ) ) + if( MeshElement.IsElementIDValid( EditableMesh ) ) { UPrimitiveComponent* Component = MeshElement.Component.Get(); check( Component != nullptr ); @@ -4988,18 +4860,6 @@ void FMeshEditorMode::MakeVRRadialMenuActionsMenu(FMenuBuilder& MenuBuilder, TSh NAME_None, EUserInterfaceActionType::ToggleButton ); - MenuBuilder.AddMenuEntry( - LOCTEXT("Delete", "Delete"), - FText(), - FSlateIcon(FMeshEditorStyle::GetStyleSetName(), "MeshEditorMode.PolyDelete"), - FUIAction - ( - FExecuteAction::CreateLambda([this] { DeleteSelectedMeshElement(); }), - FCanExecuteAction::CreateSP(this, &FMeshEditorMode::IsMeshElementTypeSelected, EEditableMeshElementType::Polygon) - ), - NAME_None, - EUserInterfaceActionType::CollapsedButton - ); } else if (GetMeshElementSelectionMode() == EEditableMeshElementType::Edge) { @@ -5016,18 +4876,6 @@ void FMeshEditorMode::MakeVRRadialMenuActionsMenu(FMenuBuilder& MenuBuilder, TSh NAME_None, EUserInterfaceActionType::ToggleButton ); - MenuBuilder.AddMenuEntry( - LOCTEXT("Delete", "Delete"), - FText(), - FSlateIcon(FMeshEditorStyle::GetStyleSetName(), "MeshEditorMode.EdgeDelete"), - FUIAction - ( - FExecuteAction::CreateLambda([this] { DeleteSelectedMeshElement(); }), - FCanExecuteAction::CreateSP(this, &FMeshEditorMode::IsMeshElementTypeSelected, EEditableMeshElementType::Edge) - ), - NAME_None, - EUserInterfaceActionType::CollapsedButton - ); MenuBuilder.AddMenuEntry( LOCTEXT( "SelectEdgeLoop", "Select Edge Loop" ), FText(), @@ -5056,18 +4904,6 @@ void FMeshEditorMode::MakeVRRadialMenuActionsMenu(FMenuBuilder& MenuBuilder, TSh NAME_None, EUserInterfaceActionType::ToggleButton ); - MenuBuilder.AddMenuEntry( - LOCTEXT("Delete", "Delete"), - FText(), - FSlateIcon(FMeshEditorStyle::GetStyleSetName(), "MeshEditorMode.VertexDelete"), - FUIAction - ( - FExecuteAction::CreateLambda([this] { DeleteSelectedMeshElement(); }), - FCanExecuteAction::CreateSP(this, &FMeshEditorMode::IsMeshElementTypeSelected, EEditableMeshElementType::Vertex) - ), - NAME_None, - EUserInterfaceActionType::CollapsedButton - ); MenuBuilder.AddMenuEntry( LOCTEXT("WeldSelected", "Weld Selected"), FText(), @@ -5136,6 +4972,68 @@ void FMeshEditorMode::SetEquippedAction( const EEditableMeshElementType ForEleme } } +FName FMeshEditorMode::GetEquippedSelectionModifier( const EEditableMeshElementType ForElementType ) const +{ + switch ( ForElementType ) + { + case EEditableMeshElementType::Vertex : + return EquippedVertexSelectionModifier; + case EEditableMeshElementType::Edge : + return EquippedEdgeSelectionModifier; + case EEditableMeshElementType::Polygon : + return EquippedPolygonSelectionModifier; + default: + return NAME_None; + } +} + +UMeshEditorSelectionModifier* FMeshEditorMode::GetEquippedSelectionModifier() const +{ + FName EquippedSelectionModifierName = GetEquippedSelectionModifier( GetMeshElementSelectionMode() ); + + if ( EquippedSelectionModifierName == NAME_None ) + { + EquippedSelectionModifierName = GetEquippedSelectionModifier( GetSelectedMeshElementType() ); + + if ( EquippedSelectionModifierName == NAME_None ) + { + return nullptr; + } + } + + UMeshEditorSelectionModifier* const* SelectionModifierPtr = Algo::FindByPredicate( MeshEditorSelectionModifiers::Get(), [ EquippedSelectionModifierName ]( const UMeshEditorSelectionModifier* Element ) -> bool + { + return EquippedSelectionModifierName == Element->GetSelectionModifierName(); + }); + + if ( SelectionModifierPtr == nullptr ) + { + return nullptr; + } + + return *SelectionModifierPtr; +} + +void FMeshEditorMode::SetEquippedSelectionModifier( const EEditableMeshElementType ForElementType, const FName ModifierToEquip ) +{ + switch ( ForElementType ) + { + case EEditableMeshElementType::Vertex : + EquippedVertexSelectionModifier = ModifierToEquip; + break; + case EEditableMeshElementType::Edge : + EquippedEdgeSelectionModifier = ModifierToEquip; + break; + case EEditableMeshElementType::Polygon : + EquippedPolygonSelectionModifier = ModifierToEquip; + break; + default: + break; + } + + FScopedTransaction Transaction( LOCTEXT( "SetEquippedSelectionModifier", "Set Selection Modifier" ) ); + DeselectAllMeshElements(); +} void FMeshEditorMode::TrackUndo( UObject* Object, TUniquePtr RevertChange ) { @@ -5188,7 +5086,7 @@ FMeshElement FMeshEditorMode::GetHoveredMeshElement( const UViewportInteractor* const UEditableMesh* EditableMesh = FindEditableMesh( *InteractorData.HoveredMeshElement.Component, InteractorData.HoveredMeshElement.ElementAddress.SubMeshAddress ); if( EditableMesh != nullptr ) { - if( IsElementIDValid( InteractorData.HoveredMeshElement, EditableMesh ) ) + if( InteractorData.HoveredMeshElement.IsElementIDValid( EditableMesh ) ) { HoveredMeshElement = InteractorData.HoveredMeshElement; } diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h index 508599c9fcfa..cac1fcbddb53 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorMode.h @@ -21,6 +21,8 @@ #include "UObject/ObjectKey.h" #include "MeshEditorMode.generated.h" +class UMeshEditorSelectionModifier; + UCLASS() class UMeshEditorModeProxyObject : public UObject @@ -112,33 +114,6 @@ public: UEditableMesh* FindOrCreateEditableMesh( class UPrimitiveComponent& Component, const FEditableMeshSubMeshAddress& SubMeshAddress ); - /** Checks to see that the mesh element actually exists in the mesh */ - inline static bool IsElementIDValid( const FMeshElement& MeshElement, const UEditableMesh* EditableMesh ) - { - bool bIsValid = false; - - if( EditableMesh != nullptr && MeshElement.ElementAddress.ElementID != FElementID::Invalid ) - { - switch( MeshElement.ElementAddress.ElementType ) - { - case EEditableMeshElementType::Vertex: - bIsValid = EditableMesh->IsValidVertex( FVertexID( MeshElement.ElementAddress.ElementID ) ); - break; - - case EEditableMeshElementType::Edge: - bIsValid = EditableMesh->IsValidEdge( FEdgeID( MeshElement.ElementAddress.ElementID ) ); - break; - - case EEditableMeshElementType::Polygon: - bIsValid = EditableMesh->IsValidPolygon( FPolygonID( MeshElement.ElementAddress.ElementID ) ); - break; - } - } - - return bIsValid; - } - - protected: // FEdMode interface @@ -169,6 +144,11 @@ protected: virtual const TArray, FUIAction>>& GetVertexActions() const override { return VertexActions; } virtual const TArray, FUIAction>>& GetEdgeActions() const override { return EdgeActions; } virtual const TArray, FUIAction>>& GetPolygonActions() const override { return PolygonActions; } + + virtual const TArray, FUIAction>>& GetVertexSelectionModifiers() const override { return VertexSelectionModifiersActions; } + virtual const TArray, FUIAction>>& GetEdgeSelectionModifiers() const override { return EdgeSelectionModifiersActions; } + virtual const TArray, FUIAction>>& GetPolygonSelectionModifiers() const override { return PolygonSelectionModifiersActions; } + virtual bool IsEditingPerInstance() const override { return bPerInstanceEdits; } virtual void SetEditingPerInstance( bool bPerInstance ) override { bPerInstanceEdits = bPerInstance; } virtual void PropagateInstanceChanges() override; @@ -186,6 +166,10 @@ protected: { return GetSelectedMeshElementIndex( MeshElement ) != INDEX_NONE; } + + /** Helper function that returns a map keying an editable mesh with its selected elements */ + virtual void GetSelectedMeshesAndElements( EEditableMeshElementType ElementType, TMap>& OutMeshesAndElements ) override; + virtual void GetSelectedMeshesAndVertices( TMap>& OutMeshesAndVertices ) override { GetSelectedMeshesAndElements( EEditableMeshElementType::Vertex, /* Out */ OutMeshesAndVertices ); @@ -212,7 +196,6 @@ protected: } - /** Gets the container of all the assets used in the mesh editor */ const class UMeshEditorAssetContainer& GetAssetContainer() const; @@ -276,15 +259,22 @@ protected: void RegisterEdgeCommand( const TSharedPtr& Command, const FExecuteAction& ExecuteAction ); void RegisterPolygonCommand( const TSharedPtr& Command, const FExecuteAction& ExecuteAction ); + FName GetEquippedSelectionModifier( const EEditableMeshElementType ForElementType ) const; + UMeshEditorSelectionModifier* GetEquippedSelectionModifier() const; + void SetEquippedSelectionModifier( const EEditableMeshElementType ForElementType, const FName ModifierToEquip ); + + /** Creates the selection modifiers UI actions */ + void BindSelectionModifiersCommands(); + + /** Applies the equipped selection modifier to InOutMeshElementsToSelect */ + void ModifySelection( TArray< FMeshElement >& InOutMeshElementsToSelect ); + /** Return the CommandList pertinent to the currently selected element type, or nullptr if nothing is selected */ const FUICommandList* GetCommandListForSelectedElementType() const; /** Commits the mesh instance for the given component */ void CommitEditableMeshIfNecessary( UEditableMesh* EditableMesh, UPrimitiveComponent* Component ); - /** Deletes selected polygons, or polygons partly defined by selected elements; returns whether successful */ - bool DeleteSelectedMeshElement(); - #if EDITABLE_MESH_USE_OPENSUBDIV /** Adds or removes a subdivision level for selected meshes */ void AddOrRemoveSubdivisionLevel( const bool bShouldAdd ); @@ -299,14 +289,8 @@ protected: /** Welds the selected vertices if possible, keeping the first selected vertex */ bool WeldSelectedVertices(); - /** Flips selected polygons; returns whether successful */ - bool FlipSelectedPolygons(); - /** Triangulates selected polygons; returns whether successful */ bool TriangulateSelectedPolygons(); - - /** Assigns a material to the selected polygons; returns whether successful */ - bool AssignSelectedMaterialToSelectedPolygons(); /** Assigns a material to the selected polygons; returns whether successful */ bool AssignMaterialToSelectedPolygons( UMaterialInterface* SelectedMaterial ); @@ -318,9 +302,6 @@ protected: new (empty) data will be created for it on demand */ FMeshEditorInteractorData& GetMeshEditorInteractorData( const UViewportInteractor* ViewportInteractor ) const; - /** Helper function that returns a map keying an editable mesh with its selected elements */ - void GetSelectedMeshesAndElements( EEditableMeshElementType ElementType, TMap>& OutMeshesAndElements ); - /** Selects elements of the given type captured by the last marquee select */ void PerformMarqueeSelect( EEditableMeshElementType ElementType ); @@ -603,6 +584,11 @@ protected: result in a 'final' application of the change that performs a more exhaustive (and more expensive) update. */ FName ActiveAction; + /** The selection modifier to apply when selecting mesh elements */ + FName EquippedVertexSelectionModifier; + FName EquippedEdgeSelectionModifier; + FName EquippedPolygonSelectionModifier; + /** Whether we're actually in the middile of updating the active action. This means that StoreUndo() will behave differently in this case -- instead of pushing undo data to the editor, we'll capture it temporarily in PreviewRevertChanges, so that we can roll it back at the beginning of the next frame. */ @@ -664,6 +650,9 @@ protected: TArray, FUIAction>> EdgeActions; TArray, FUIAction>> PolygonActions; + TArray, FUIAction>> VertexSelectionModifiersActions; + TArray, FUIAction>> EdgeSelectionModifiersActions; + TArray, FUIAction>> PolygonSelectionModifiersActions; // // DrawVertices diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorModeToolkit.cpp b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorModeToolkit.cpp index 5b19d76ff3a7..6b4cec0cac67 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorModeToolkit.cpp +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorModeToolkit.cpp @@ -27,8 +27,60 @@ public: public: /** SCompoundWidget functions */ - void Construct( const FArguments& InArgs, const FText& GroupName, const TArray, FUIAction>>& Actions ) + void Construct( const FArguments& InArgs, const FText& GroupName, const TArray, FUIAction>>& Actions, const TArray, FUIAction>>& SelectionModifiers ) { + TSharedRef< SHorizontalBox > SelectionModifiersButtons = SNew( SHorizontalBox ); + + if ( SelectionModifiers.Num() > 1 ) // Only display the list of selection modifiers if there's more than 1. + { + SelectionModifiersButtons->AddSlot() + .HAlign( HAlign_Center ) + .VAlign( VAlign_Center ) + .Padding( 3.0f, 1.0f, 3.0f, 1.0f ) + [ + SNew( STextBlock ) + .TextStyle( FMeshEditorStyle::Get(), "EditingMode.Entry.Text" ) + .Text( LOCTEXT( "Selection", "Selection" ) ) + ]; + + for ( const TTuple, FUIAction>& Action : SelectionModifiers ) + { + const FUICommandInfo& CommandInfo = *Action.Get<0>(); + const FUIAction& UIAction = Action.Get<1>(); + + SelectionModifiersButtons->AddSlot() + .AutoWidth() + .Padding( 3.0f, 1.0f, 3.0f, 1.0f ) + [ + SNew( SCheckBox ) + .Style( FMeshEditorStyle::Get(), "EditingMode.Entry" ) + .ToolTip( SNew( SToolTip ).Text( CommandInfo.GetDescription() ) ) + .IsChecked_Lambda( [UIAction] { return UIAction.GetCheckState(); } ) + .OnCheckStateChanged_Lambda( [UIAction]( ECheckBoxState State ) { if( State == ECheckBoxState::Checked ) { UIAction.Execute(); } } ) + [ + SNew( SOverlay ) + +SOverlay::Slot() + .VAlign( VAlign_Center ) + [ + SNew( SSpacer ) + .Size( FVector2D( 1, 30 ) ) + ] + +SOverlay::Slot() + .Padding( FMargin( 8, 0, 8, 0 ) ) + .HAlign( HAlign_Center ) + .VAlign( VAlign_Center ) + [ + SNew( STextBlock ) + .TextStyle( FMeshEditorStyle::Get(), "EditingMode.Entry.Text" ) + .Text( CommandInfo.GetDefaultChord(EMultipleKeyBindingIndex::Primary).IsValidChord() ? + FText::Format( LOCTEXT( "RadioButtonLabelAndShortcutFormat", "{0} ({1})" ), CommandInfo.GetLabel(), CommandInfo.GetDefaultChord(EMultipleKeyBindingIndex::Primary).GetInputText() ) : + CommandInfo.GetLabel() ) + ] + ] + ]; + } + } + TSharedRef Buttons = SNew( SVerticalBox ); TSharedRef RadioButtons = SNew( SVerticalBox ); @@ -111,6 +163,12 @@ public: +SVerticalBox::Slot() .AutoHeight() .HAlign( HAlign_Center ) + [ + SelectionModifiersButtons + ] + +SVerticalBox::Slot() + .AutoHeight() + .HAlign( HAlign_Center ) [ RadioButtons ] @@ -181,17 +239,17 @@ void SMeshEditorModeControls::Construct( const FArguments& InArgs, IMeshEditorMo WidgetSwitcher->AddSlot( static_cast( EEditableMeshElementType::Vertex ) ) [ - SNew( SMeshEditorModeControlWidget, LOCTEXT( "VertexGroupName", "Vertex" ), MeshEditorMode.GetVertexActions() ) + SNew( SMeshEditorModeControlWidget, LOCTEXT( "VertexGroupName", "Vertex" ), MeshEditorMode.GetVertexActions(), MeshEditorMode.GetVertexSelectionModifiers() ) ]; WidgetSwitcher->AddSlot( static_cast( EEditableMeshElementType::Edge ) ) [ - SNew( SMeshEditorModeControlWidget, LOCTEXT( "EdgeGroupName", "Edge" ), MeshEditorMode.GetEdgeActions() ) + SNew( SMeshEditorModeControlWidget, LOCTEXT( "EdgeGroupName", "Edge" ), MeshEditorMode.GetEdgeActions(), MeshEditorMode.GetEdgeSelectionModifiers() ) ]; WidgetSwitcher->AddSlot( static_cast( EEditableMeshElementType::Polygon ) ) [ - SNew( SMeshEditorModeControlWidget, LOCTEXT( "PolygonGroupName", "Polygon" ), MeshEditorMode.GetPolygonActions() ) + SNew( SMeshEditorModeControlWidget, LOCTEXT( "PolygonGroupName", "Polygon" ), MeshEditorMode.GetPolygonActions(), MeshEditorMode.GetPolygonSelectionModifiers() ) ]; WidgetSwitcher->AddSlot( static_cast( EEditableMeshElementType::Invalid ) ) @@ -331,7 +389,7 @@ void SMeshEditorModeControls::Construct( const FArguments& InArgs, IMeshEditorMo SNew( SBox ) .Visibility_Lambda( [&MeshEditorMode]() { return MeshEditorMode.GetSelectedEditableMeshes().Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed; } ) [ - SNew( SMeshEditorModeControlWidget, LOCTEXT( "MeshGroupName", "Mesh" ), MeshEditorMode.GetCommonActions() ) + SNew( SMeshEditorModeControlWidget, LOCTEXT( "MeshGroupName", "Mesh" ), MeshEditorMode.GetCommonActions(), TArray< TTuple< TSharedPtr, FUIAction > >() ) ] ] ] diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorSelectionModifiers.cpp b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorSelectionModifiers.cpp new file mode 100644 index 000000000000..7f3139ea41cd --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorSelectionModifiers.cpp @@ -0,0 +1,187 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MeshEditorSelectionModifiers.h" + +#include "EditableMesh.h" +#include "IMeshEditorModeUIContract.h" +#include "MeshEditorStyle.h" +#include "UObject/UObjectIterator.h" + +#define LOCTEXT_NAMESPACE "MeshEditorSelectionModifiers" + +namespace MeshEditorSelectionModifiers +{ + const TArray< UMeshEditorSelectionModifier* >& Get() + { + static UMeshEditorSelectionModifiersList* MeshEditorSelectionModifiersList = nullptr; + if( MeshEditorSelectionModifiersList == nullptr ) + { + MeshEditorSelectionModifiersList = NewObject< UMeshEditorSelectionModifiersList >(); + MeshEditorSelectionModifiersList->AddToRoot(); + + MeshEditorSelectionModifiersList->HarvestSelectionModifiers(); + } + + return MeshEditorSelectionModifiersList->SelectionModifiers; + } +} + +void UMeshEditorSelectionModifiersList::HarvestSelectionModifiers() +{ + SelectionModifiers.Reset(); + for ( TObjectIterator< UMeshEditorSelectionModifier > SelectionModifierCDOIter( RF_NoFlags ); SelectionModifierCDOIter; ++SelectionModifierCDOIter ) + { + UMeshEditorSelectionModifier* SelectionModifierCDO = *SelectionModifierCDOIter; + if ( !( SelectionModifierCDO->GetClass()->GetClassFlags() & CLASS_Abstract ) ) + { + SelectionModifiers.Add( NewObject< UMeshEditorSelectionModifier >( this, SelectionModifierCDO->GetClass() ) ); + } + } +} + +FMeshEditorSelectionModifiers::FMeshEditorSelectionModifiers() + : TCommands< FMeshEditorSelectionModifiers >( + "MeshEditorSelectionModifiers", + LOCTEXT("MeshEditorSelectionModifiers", "Mesh Editor Selection Modifiers"), + "MeshEditorCommon", + FMeshEditorStyle::GetStyleSetName() ) +{ +} + +void FMeshEditorSelectionModifiers::RegisterCommands() +{ + for ( UMeshEditorSelectionModifier* SelectionModifier : MeshEditorSelectionModifiers::Get() ) + { + SelectionModifier->RegisterUICommand( this ); + } +} + +void USelectSingleMeshElement::RegisterUICommand( FBindingContext* BindingContext ) +{ + UI_COMMAND_EXT( BindingContext, /* Out */ UICommandInfo, "SingleElement", "Single", "", EUserInterfaceActionType::RadioButton, FInputChord() ); +} + +bool USelectPolygonsByGroup::ModifySelection( TMap< UEditableMesh*, TArray< FMeshElement > >& InOutSelection ) +{ + if ( InOutSelection.Num() == 0 ) + { + return false; + } + + TMap< UEditableMesh*, TArray< FMeshElement > > MeshElementsToSelect; + TSet< FPolygonID > PolygonsToSelect; + + for ( const auto& MeshAndPolygons : InOutSelection ) + { + UEditableMesh* EditableMesh = MeshAndPolygons.Key; + MeshElementsToSelect.Add( EditableMesh ); + + const TArray& Polygons = MeshAndPolygons.Value; + + for ( const FMeshElement& PolygonElement : Polygons ) + { + const FPolygonID PolygonID( PolygonElement.ElementAddress.ElementID ); + + if ( PolygonsToSelect.Contains( PolygonID ) ) + { + // This polygon was already processed, we can skip it + continue; + } + + FPolygonGroupID SelectedPolygonGroupID = EditableMesh->GetGroupForPolygon( PolygonID ); + + for ( int32 PolygonNumber = 0; PolygonNumber < EditableMesh->GetPolygonCountInGroup( SelectedPolygonGroupID ); ++PolygonNumber ) + { + FPolygonID PolygonIDInGroup( EditableMesh->GetPolygonInGroup( SelectedPolygonGroupID, PolygonNumber ) ); + + if ( !PolygonsToSelect.Contains( PolygonIDInGroup ) ) + { + MeshElementsToSelect[ EditableMesh ].Emplace( PolygonElement.Component.Get(), EditableMesh->GetSubMeshAddress(), PolygonIDInGroup ); + PolygonsToSelect.Add( PolygonIDInGroup ); + } + } + } + } + + InOutSelection = MeshElementsToSelect; + + return true; +} + +void USelectPolygonsByGroup::RegisterUICommand( FBindingContext* BindingContext ) +{ + UI_COMMAND_EXT( BindingContext, /* Out */ UICommandInfo, "PolygonsByGroup", "Material", "", EUserInterfaceActionType::RadioButton, FInputChord() ); +} + +bool USelectPolygonsByConnectivity::ModifySelection( TMap< UEditableMesh*, TArray< FMeshElement > >& InOutSelection ) +{ + if ( InOutSelection.Num() == 0 ) + { + return false; + } + + TMap< UEditableMesh*, TArray< FMeshElement > > MeshElementsToSelect; + + for ( const auto& MeshAndPolygons : InOutSelection ) + { + UEditableMesh* EditableMesh = MeshAndPolygons.Key; + MeshElementsToSelect.Add( EditableMesh ); + + const TArray& Polygons = MeshAndPolygons.Value; + + TSet< FPolygonID > FilledPolygons; + + for( const FMeshElement& PolygonElement : Polygons ) + { + FPolygonID PolygonID( PolygonElement.ElementAddress.ElementID ); + + TSet< FPolygonID > ConnectedPolygons; + + if ( !FilledPolygons.Contains( PolygonID ) ) + { + ConnectedPolygons.Add( PolygonID ); + FilledPolygons.Add( PolygonID ); + } + + FPolygonID ConnectedPolygonID = FPolygonID::Invalid; + + while ( ConnectedPolygons.Num() > 0 ) + { + ConnectedPolygonID = *ConnectedPolygons.CreateIterator(); + + TArray< FEdgeID > PolygonEdges; + EditableMesh->GetPolygonPerimeterEdges( ConnectedPolygonID, PolygonEdges ); + + for ( FEdgeID EdgeID : PolygonEdges ) + { + TArray< FPolygonID > EdgeConnectedPolygons; + EditableMesh->GetEdgeConnectedPolygons( EdgeID, EdgeConnectedPolygons ); + + for ( FPolygonID EdgeConnectedPolygonID : EdgeConnectedPolygons ) + { + if ( !FilledPolygons.Contains( EdgeConnectedPolygonID ) ) + { + ConnectedPolygons.Add( EdgeConnectedPolygonID ); + } + } + + FilledPolygons.Append( EdgeConnectedPolygons ); + } + + MeshElementsToSelect[ EditableMesh ].Emplace( PolygonElement.Component.Get(), EditableMesh->GetSubMeshAddress(), ConnectedPolygonID ); + ConnectedPolygons.Remove( ConnectedPolygonID ); + } + } + } + + InOutSelection = MeshElementsToSelect; + + return true; +} + +void USelectPolygonsByConnectivity::RegisterUICommand( FBindingContext* BindingContext ) +{ + UI_COMMAND_EXT( BindingContext, /* Out */ UICommandInfo, "PolygonsByConnectivity", "Element", "", EUserInterfaceActionType::RadioButton, FInputChord() ); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStyle.cpp b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStyle.cpp index 2e7e0fbb5deb..5f82d7a0a0d9 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStyle.cpp +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStyle.cpp @@ -112,6 +112,41 @@ void FMeshEditorStyle::Initialize() StyleSet->Set("MeshEditorMode.EdgeEditMode", new IMAGE_PLUGIN_BRUSH("Icons/T_Radial_Mesh_Edge", Icon512x512)); StyleSet->Set("MeshEditorMode.VertexEditMode", new IMAGE_PLUGIN_BRUSH("Icons/T_Radial_Mesh_Vertex", Icon512x512)); + StyleSet->Set( "MeshEditorPolygonMode.EditMode", new IMAGE_PLUGIN_BRUSH( "Icons/EditMode", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygonMode.EditMode.Small", new IMAGE_PLUGIN_BRUSH( "Icons/EditMode", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygonMode.EditMode.Selected", new IMAGE_PLUGIN_BRUSH( "Icons/EditMode", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygonMode.EditMode.Selected.Small", new IMAGE_PLUGIN_BRUSH( "Icons/EditMode", Icon40x40 ) ); + + StyleSet->Set( "MeshEditorAnyElement.DeleteMeshElement", new IMAGE_PLUGIN_BRUSH( "Icons/DeleteMeshElement", Icon40x40 ) ); + StyleSet->Set( "MeshEditorAnyElement.DeleteMeshElement.Small", new IMAGE_PLUGIN_BRUSH( "Icons/DeleteMeshElement", Icon40x40 ) ); + StyleSet->Set( "MeshEditorAnyElement.DeleteMeshElement.Selected", new IMAGE_PLUGIN_BRUSH( "Icons/DeleteMeshElement", Icon40x40 ) ); + StyleSet->Set( "MeshEditorAnyElement.DeleteMeshElement.Selected.Small", new IMAGE_PLUGIN_BRUSH( "Icons/DeleteMeshElement", Icon40x40 ) ); + + StyleSet->Set( "MeshEditorPolygon.FlipPolygon", new IMAGE_PLUGIN_BRUSH( "Icons/FlipPolygon", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygon.FlipPolygon.Small", new IMAGE_PLUGIN_BRUSH( "Icons/FlipPolygon", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygon.FlipPolygon.Selected", new IMAGE_PLUGIN_BRUSH( "Icons/FlipPolygon", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygon.FlipPolygon.Selected.Small", new IMAGE_PLUGIN_BRUSH( "Icons/FlipPolygon", Icon40x40 ) ); + + StyleSet->Set( "MeshEditorPolygon.AssignMaterial", new IMAGE_PLUGIN_BRUSH( "Icons/AssignMaterial", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygon.AssignMaterial.Small", new IMAGE_PLUGIN_BRUSH( "Icons/AssignMaterial", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygon.AssignMaterial.Selected", new IMAGE_PLUGIN_BRUSH( "Icons/AssignMaterial", Icon40x40 ) ); + StyleSet->Set( "MeshEditorPolygon.AssignMaterial.Selected.Small", new IMAGE_PLUGIN_BRUSH( "Icons/AssignMaterial", Icon40x40 ) ); + + StyleSet->Set( "MeshEditorSelectionModifiers.PolygonsByGroup", new IMAGE_PLUGIN_BRUSH( "Icons/PolygonsByGroup", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.PolygonsByGroup.Small", new IMAGE_PLUGIN_BRUSH( "Icons/PolygonsByGroup", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.PolygonsByGroup.Selected", new IMAGE_PLUGIN_BRUSH( "Icons/PolygonsByGroup", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.PolygonsByGroup.Selected.Small", new IMAGE_PLUGIN_BRUSH( "Icons/PolygonsByGroup", Icon40x40 ) ); + + StyleSet->Set( "MeshEditorSelectionModifiers.SingleElement", new IMAGE_PLUGIN_BRUSH( "Icons/SingleElement", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.SingleElement.Small", new IMAGE_PLUGIN_BRUSH( "Icons/SingleElement", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.SingleElement.Selected", new IMAGE_PLUGIN_BRUSH( "Icons/SingleElement", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.SingleElement.Selected.Small", new IMAGE_PLUGIN_BRUSH( "Icons/SingleElement", Icon40x40 ) ); + + StyleSet->Set( "MeshEditorSelectionModifiers.PolygonsByConnectivity", new IMAGE_PLUGIN_BRUSH( "Icons/PolygonsByConnectivity", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.PolygonsByConnectivity.Small", new IMAGE_PLUGIN_BRUSH( "Icons/PolygonsByConnectivity", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.PolygonsByConnectivity.Selected", new IMAGE_PLUGIN_BRUSH( "Icons/PolygonsByConnectivity", Icon40x40 ) ); + StyleSet->Set( "MeshEditorSelectionModifiers.PolygonsByConnectivity.Selected.Small", new IMAGE_PLUGIN_BRUSH( "Icons/PolygonsByConnectivity", Icon40x40 ) ); + FSlateStyleRegistry::RegisterSlateStyle( *StyleSet.Get() ); } diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorUtilities.cpp b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorUtilities.cpp new file mode 100644 index 000000000000..b7feb37ce9d6 --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorUtilities.cpp @@ -0,0 +1,105 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MeshEditorUtilities.h" + +#include "EditableMesh.h" +#include "MeshAttributes.h" +#include "MeshElement.h" +#include "Materials/Material.h" + +bool FMeshEditorUtilities::AssignMaterialToPolygons( UMaterialInterface* SelectedMaterial, UEditableMesh* EditableMesh, const TArray< FMeshElement >& PolygonElements ) +{ + EditableMesh->StartModification( EMeshModificationType::Final, EMeshTopologyChange::TopologyChange ); + { + const UMeshDescription* MeshDescription = EditableMesh->GetMeshDescription(); + + UPrimitiveComponent* Component = nullptr; + for( const FMeshElement& PolygonElement : PolygonElements ) + { + Component = PolygonElement.Component.Get(); + break; + } + check( Component != nullptr ); + + // See if there's a polygon group using this material, and if not create one. + // @todo mesheditor: This currently imposes the limitation that each polygon group has a unique material. + // Eventually we will need to be able to specify PolygonGroup properties in the editor, and ask the user for + // further details if there is more than one polygon group which matches the material. + FPolygonGroupID PolygonGroupToAssign = FPolygonGroupID::Invalid; + + if ( SelectedMaterial != nullptr ) + { + const TPolygonGroupAttributeArray& PolygonGroupMaterialAssetNames = MeshDescription->PolygonGroupAttributes().GetAttributes( MeshAttribute::PolygonGroup::MaterialAssetName ); + + for( const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs() ) + { + const FName PolygonGroupMaterialName = PolygonGroupMaterialAssetNames[ PolygonGroupID ]; + if( SelectedMaterial->GetPathName() == PolygonGroupMaterialName.ToString() ) + { + // We only expect to find one polygon group containing this material at the moment. + // We need to provide a way of distinguishing different polygon groups with the same material. + ensure( PolygonGroupToAssign == FPolygonGroupID::Invalid ); + PolygonGroupToAssign = PolygonGroupID; + } + } + } + else + { + // Use default material as asset to create new polygon group + SelectedMaterial = UMaterial::GetDefaultMaterial(EMaterialDomain::MD_Surface); + } + + + // If we didn't find the material being used anywhere, create a new polygon group + if( PolygonGroupToAssign == FPolygonGroupID::Invalid ) + { + // Helper function which returns a unique FName for the material slot name, based on the material's asset name, + // and adding a unique suffix if there are other polygon groups with the same material slot name. + auto MakeUniqueSlotName = [ MeshDescription ]( FName Name ) -> FName + { + const TPolygonGroupAttributeArray& MaterialSlotNames = MeshDescription->PolygonGroupAttributes().GetAttributes( MeshAttribute::PolygonGroup::MaterialAssetName ); + for( const FPolygonGroupID PolygonGroupID : MeshDescription->PolygonGroups().GetElementIDs() ) + { + const FName ExistingName = MaterialSlotNames[ PolygonGroupID ]; + if( ExistingName.GetComparisonIndex() == Name.GetComparisonIndex() ) + { + Name = FName( Name, FMath::Max( Name.GetNumber(), ExistingName.GetNumber() + 1 ) ); + } + } + return Name; + }; + + const FName UniqueSlotName = MakeUniqueSlotName( SelectedMaterial->GetFName() ); + + static TArray PolygonGroupsToCreate; + PolygonGroupsToCreate.Reset( 1 ); + PolygonGroupsToCreate.Emplace(); + + FPolygonGroupToCreate& PolygonGroupToCreate = PolygonGroupsToCreate.Last(); + PolygonGroupToCreate.PolygonGroupAttributes.Attributes.Emplace( MeshAttribute::PolygonGroup::MaterialAssetName, 0, FMeshElementAttributeValue( FName( *SelectedMaterial->GetPathName() ) ) ); + PolygonGroupToCreate.PolygonGroupAttributes.Attributes.Emplace( MeshAttribute::PolygonGroup::ImportedMaterialSlotName, 0, FMeshElementAttributeValue( UniqueSlotName )); + static TArray NewPolygonGroupIDs; + EditableMesh->CreatePolygonGroups( PolygonGroupsToCreate, NewPolygonGroupIDs ); + PolygonGroupToAssign = NewPolygonGroupIDs[ 0 ]; + } + + static TArray PolygonsToAssign; + PolygonsToAssign.Reset(); + + for( const FMeshElement& PolygonElement : PolygonElements ) + { + const FPolygonID PolygonID( PolygonElement.ElementAddress.ElementID ); + + PolygonsToAssign.Emplace(); + FPolygonGroupForPolygon& PolygonGroupForPolygon = PolygonsToAssign.Last(); + PolygonGroupForPolygon.PolygonID = PolygonID; + PolygonGroupForPolygon.PolygonGroupID = PolygonGroupToAssign; + } + + const bool bDeleteOrphanedPolygonGroups = true; + EditableMesh->AssignPolygonsToPolygonGroups( PolygonsToAssign, bDeleteOrphanedPolygonGroups ); + } + EditableMesh->EndModification(); + + return true; +} diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshElementViewportTransformable.cpp b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshElementViewportTransformable.cpp index ae2341d4b789..d0c0def9bf6a 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshElementViewportTransformable.cpp +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshElementViewportTransformable.cpp @@ -38,7 +38,7 @@ FBox FMeshElementViewportTransformable::BuildBoundingBox( const FTransform& Boun UEditableMesh* EditableMesh = MeshEditorMode.FindOrCreateEditableMesh( *MeshElement.Component, MeshElement.ElementAddress.SubMeshAddress ); if( EditableMesh != nullptr ) { - if( FMeshEditorMode::IsElementIDValid( MeshElement, EditableMesh ) ) + if( MeshElement.IsElementIDValid( EditableMesh ) ) { const TVertexAttributeArray& VertexPositions = EditableMesh->GetMeshDescription()->VertexAttributes().GetAttributes( MeshAttribute::Vertex::Position ); BoundingBox.Init(); diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/OverlayComponent.cpp b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/OverlayComponent.cpp index 854c98553656..4bf15346a957 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/OverlayComponent.cpp +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/OverlayComponent.cpp @@ -1,4 +1,4 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. #include "OverlayComponent.h" #include "RenderingThread.h" @@ -356,7 +356,7 @@ FOverlayLineID UOverlayComponent::AddLine( const FOverlayLine& OverlayLine ) void UOverlayComponent::InsertLine( const FOverlayLineID ID, const FOverlayLine& OverlayLine ) { - Lines.Insert( ID.GetValue(), OverlayLine ); + Lines.Insert( ID.GetValue(), OverlayLine ); MarkRenderStateDirty(); bBoundsDirty = true; } @@ -485,6 +485,7 @@ void UOverlayComponent::RemoveTriangle( const FOverlayTriangleID ID ) if( Container.Num() == 0 ) { TrianglesByMaterial.RemoveAt( MaterialIndex ); + MaterialToIndex.Remove(GetMaterial(MaterialIndex + 2)); SetMaterial( MaterialIndex + 2, nullptr ); } Triangles.RemoveAt( ID.GetValue() ); diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/IMeshEditorModeEditingContract.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/IMeshEditorModeEditingContract.h index 0c4129e0424b..bf2f5ea291a7 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/IMeshEditorModeEditingContract.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/IMeshEditorModeEditingContract.h @@ -29,6 +29,7 @@ public: /** Returns the mesh element the specified viewport interactor is currently hovering over. The returned element might be invalid if nothing valid is hovered right now */ virtual FMeshElement GetHoveredMeshElement( const class UViewportInteractor* ViewportInteractor ) const = 0; + virtual void GetSelectedMeshesAndElements( EEditableMeshElementType ElementType, TMap>& OutMeshesAndElements ) = 0; virtual void GetSelectedMeshesAndVertices( TMap>& OutMeshesAndVertices ) = 0; virtual void GetSelectedMeshesAndEdges( TMap>& OutMeshesAndEdges ) = 0; virtual void GetSelectedMeshesAndPolygons( TMap>& OutMeshesAndPolygons ) = 0; @@ -58,5 +59,4 @@ public: /** When performing an interactive action that was initiated using an interactor, this is the interactor that was used. */ virtual class UViewportInteractor* GetActiveActionInteractor() = 0; - }; diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/IMeshEditorModeUIContract.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/IMeshEditorModeUIContract.h index 19feabf91d0e..6eee06c53a63 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/IMeshEditorModeUIContract.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/IMeshEditorModeUIContract.h @@ -33,6 +33,10 @@ public: virtual const TArray, FUIAction>>& GetEdgeActions() const = 0; virtual const TArray, FUIAction>>& GetPolygonActions() const = 0; + virtual const TArray, FUIAction>>& GetVertexSelectionModifiers() const = 0; + virtual const TArray, FUIAction>>& GetEdgeSelectionModifiers() const = 0; + virtual const TArray, FUIAction>>& GetPolygonSelectionModifiers() const = 0; + virtual bool IsEditingPerInstance() const = 0; virtual void SetEditingPerInstance( bool bPerInstance ) = 0; diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorAssetContainer.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorAssetContainer.h similarity index 99% rename from Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorAssetContainer.h rename to Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorAssetContainer.h index fff11506b386..1df224ee6145 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorAssetContainer.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorAssetContainer.h @@ -12,7 +12,7 @@ UCLASS() class MESHEDITOR_API UMeshEditorAssetContainer : public UDataAsset { GENERATED_BODY() - + public: UPROPERTY(EditAnywhere, Category = Material) class UMaterialInterface* HoveredGeometryMaterial; diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorCommands.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorCommands.h index c85633592861..75d0be3ef858 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorCommands.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorCommands.h @@ -130,7 +130,7 @@ protected: // Actions that can be invoked from this mode as long as at least one mesh is selected -class FMeshEditorCommonCommands : public TCommands +class MESHEDITOR_API FMeshEditorCommonCommands : public TCommands { public: FMeshEditorCommonCommands(); @@ -170,7 +170,7 @@ public: // Actions that can be invoked from this mode for any type of selected element -class FMeshEditorAnyElementCommands : public TCommands +class MESHEDITOR_API FMeshEditorAnyElementCommands : public TCommands { public: FMeshEditorAnyElementCommands(); @@ -178,16 +178,11 @@ public: // TCommands<> interface virtual void RegisterCommands() override; // End of TCommands<> interface - -public: - - /** Deletes selected mesh elements, including polygons partly defined by selected elements */ - TSharedPtr DeleteMeshElement; }; // Actions that can be invoked from this mode when vertices are selected -class FMeshEditorVertexCommands : public TCommands +class MESHEDITOR_API FMeshEditorVertexCommands : public TCommands { public: FMeshEditorVertexCommands(); @@ -207,7 +202,7 @@ public: // Actions that can be invoked from this mode when edges are selected -class FMeshEditorEdgeCommands : public TCommands +class MESHEDITOR_API FMeshEditorEdgeCommands : public TCommands { public: FMeshEditorEdgeCommands(); @@ -227,7 +222,7 @@ public: // Actions that can be invoked from this mode when polygons are selected -class FMeshEditorPolygonCommands : public TCommands +class MESHEDITOR_API FMeshEditorPolygonCommands : public TCommands { public: FMeshEditorPolygonCommands(); @@ -241,19 +236,13 @@ public: /** Sets the primary action to move polygons */ TSharedPtr MovePolygon; - /** Flips the currently selected polygon(s) */ - TSharedPtr FlipPolygon; - /** Triangulates the currently selected polygon(s) */ TSharedPtr TriangulatePolygon; - - /** Assigns the highlighted material to the currently selected polygon(s) */ - TSharedPtr AssignMaterial; }; UCLASS() -class UMeshEditorCommandList : public UObject +class MESHEDITOR_API UMeshEditorCommandList : public UObject { GENERATED_BODY() @@ -269,7 +258,7 @@ public: namespace MeshEditorCommands { - extern const TArray& Get(); + MESHEDITOR_API const TArray& Get(); } diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorSelectionModifiers.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorSelectionModifiers.h new file mode 100644 index 000000000000..4cc4e9d48dd0 --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorSelectionModifiers.h @@ -0,0 +1,118 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "MeshElement.h" +#include "Framework/Commands/UIAction.h" +#include "Framework/Commands/UICommandInfo.h" +#include "Framework/Commands/Commands.h" + +#include "MeshEditorSelectionModifiers.generated.h" + +class IMeshEditorModeUIContract; +class UEditableMesh; +class UMeshEditorSelectionModifier; + +namespace MeshEditorSelectionModifiers +{ + MESHEDITOR_API const TArray< UMeshEditorSelectionModifier* >& Get(); +} + +class MESHEDITOR_API FMeshEditorSelectionModifiers : public TCommands< FMeshEditorSelectionModifiers > +{ +public: + FMeshEditorSelectionModifiers(); + + virtual void RegisterCommands() override; +}; + +UCLASS( abstract ) +class MESHEDITOR_API UMeshEditorSelectionModifier : public UObject +{ + GENERATED_BODY() + +public: + virtual ~UMeshEditorSelectionModifier() = default; + + /** Which mesh element does this command apply to? */ + virtual EEditableMeshElementType GetElementType() const PURE_VIRTUAL(, return EEditableMeshElementType::Invalid; ); + + /** Registers the UI command for this mesh editor command */ + virtual void RegisterUICommand( class FBindingContext* BindingContext ) PURE_VIRTUAL(,); + + virtual bool ModifySelection( TMap< UEditableMesh*, TArray< FMeshElement > >& InOutSelection ) PURE_VIRTUAL(, return false; ); + + /** Gets the UI command info for this command */ + const TSharedPtr& GetUICommandInfo() const { return UICommandInfo; } + + /** Gets the name of this selection modifier. This is not to display to a user, but instead used to uniquely identify this selection modifier */ + FName GetSelectionModifierName() const + { + return UICommandInfo->GetCommandName(); + } + +protected: + + /** Our UI command for this action */ + TSharedPtr< FUICommandInfo > UICommandInfo; +}; + +/** + * Dummy selection modifier that doesn't actually modifies the selection. + */ +UCLASS() +class MESHEDITOR_API USelectSingleMeshElement : public UMeshEditorSelectionModifier +{ + GENERATED_BODY() + +public: + virtual EEditableMeshElementType GetElementType() const override { return EEditableMeshElementType::Any; } + + virtual bool ModifySelection( TMap< UEditableMesh*, TArray< FMeshElement > >& InOutSelection ) override { return true; } + + virtual void RegisterUICommand( FBindingContext* BindingContext ) override; +}; + +/** + * Selects all the polygons that are part of the selection polygons group ids. + */ +UCLASS() +class MESHEDITOR_API USelectPolygonsByGroup : public UMeshEditorSelectionModifier +{ + GENERATED_BODY() +public: + virtual EEditableMeshElementType GetElementType() const override { return EEditableMeshElementType::Polygon; } + + virtual bool ModifySelection( TMap< UEditableMesh*, TArray< FMeshElement > >& InOutSelection ) override; + + virtual void RegisterUICommand( FBindingContext* BindingContext ) override; +}; + +/** + * Selects all the polygons that are connected to the selection polygons. + */ +UCLASS() +class MESHEDITOR_API USelectPolygonsByConnectivity : public UMeshEditorSelectionModifier +{ + GENERATED_BODY() +public: + virtual EEditableMeshElementType GetElementType() const override { return EEditableMeshElementType::Polygon; } + + virtual bool ModifySelection( TMap< UEditableMesh*, TArray< FMeshElement > >& InOutSelection ) override; + + virtual void RegisterUICommand( FBindingContext* BindingContext ) override; +}; + +UCLASS() +class MESHEDITOR_API UMeshEditorSelectionModifiersList : public UObject +{ + GENERATED_BODY() + +public: + void HarvestSelectionModifiers(); + + /** All of the selection modifiers that were registered at startup */ + UPROPERTY() + TArray< UMeshEditorSelectionModifier* > SelectionModifiers; +}; diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStaticMeshAdapter.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorStaticMeshAdapter.h similarity index 97% rename from Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStaticMeshAdapter.h rename to Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorStaticMeshAdapter.h index 45073a1c79b3..9f309e96ec27 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStaticMeshAdapter.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorStaticMeshAdapter.h @@ -9,8 +9,8 @@ #include "MeshEditorStaticMeshAdapter.generated.h" -UCLASS(MinimalAPI) -class UMeshEditorStaticMeshAdapter : public UEditableMeshAdapter +UCLASS() +class MESHEDITOR_API UMeshEditorStaticMeshAdapter : public UEditableMeshAdapter { GENERATED_BODY() @@ -59,8 +59,7 @@ public: virtual void OnRetriangulatePolygons( const UEditableMesh* EditableMesh, const TArray& PolygonIDs ) override; -private: - +protected: /** The wireframe mesh asset we're representing */ UPROPERTY() UWireframeMesh* WireframeMesh; diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStyle.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorStyle.h similarity index 93% rename from Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStyle.h rename to Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorStyle.h index e715ec146e89..5f96feef3216 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/MeshEditorStyle.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorStyle.h @@ -4,7 +4,7 @@ #include "Styling/SlateStyle.h" -class FMeshEditorStyle +class MESHEDITOR_API FMeshEditorStyle { public: diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorUtilities.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorUtilities.h new file mode 100644 index 000000000000..5816f94453e2 --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshEditorUtilities.h @@ -0,0 +1,25 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Containers/ContainersFwd.h" +#include "CoreMinimal.h" + +struct FMeshElement; +class UEditableMesh; +class UMaterialInterface; + +class MESHEDITOR_API FMeshEditorUtilities +{ +public: + /** + * Assigns a material to polygons + * + * @param SelectedMaterial The material to assign + * @param EditableMesh The mesh for which the polygons belong to + * @param PolygonElements The polygons to assign the material to + * + * @return whether successful + */ + static bool AssignMaterialToPolygons( UMaterialInterface* SelectedMaterial, UEditableMesh* EditableMesh, const TArray< FMeshElement >& PolygonElements ); +}; diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshElement.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshElement.h index 0cd01189034e..48599fd15ca2 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshElement.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/MeshElement.h @@ -2,6 +2,7 @@ #pragma once +#include "EditableMesh.h" #include "EditableMeshTypes.h" #include "Components/PrimitiveComponent.h" @@ -93,7 +94,7 @@ struct FEditableMeshElementAddress }; -struct FMeshElement +struct MESHEDITOR_API FMeshElement { /** The component that is referencing the mesh. Does not necessarily own the mesh! The mesh could be shared between many components. */ @@ -166,5 +167,31 @@ struct FMeshElement Component.IsValid() ? *Component->GetName() : TEXT( "" ), *ElementAddress.ToString() ); } + + /** Checks to see that the mesh element actually exists in the mesh */ + bool IsElementIDValid( const UEditableMesh* EditableMesh ) const + { + bool bIsValid = false; + + if( EditableMesh != nullptr && ElementAddress.ElementID != FElementID::Invalid ) + { + switch( ElementAddress.ElementType ) + { + case EEditableMeshElementType::Vertex: + bIsValid = EditableMesh->IsValidVertex( FVertexID( ElementAddress.ElementID ) ); + break; + + case EEditableMeshElementType::Edge: + bIsValid = EditableMesh->IsValidEdge( FEdgeID( ElementAddress.ElementID ) ); + break; + + case EEditableMeshElementType::Polygon: + bIsValid = EditableMesh->IsValidPolygon( FPolygonID( ElementAddress.ElementID ) ); + break; + } + } + + return bIsValid; + } }; diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/OverlayComponent.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/OverlayComponent.h similarity index 98% rename from Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/OverlayComponent.h rename to Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/OverlayComponent.h index f4d690b67c62..05dc5ac0f4dc 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/OverlayComponent.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/OverlayComponent.h @@ -170,7 +170,7 @@ struct FOverlayTriangleID UCLASS() -class UOverlayComponent : public UMeshComponent +class MESHEDITOR_API UOverlayComponent : public UMeshComponent { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/WireframeMeshComponent.h b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/WireframeMeshComponent.h similarity index 96% rename from Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/WireframeMeshComponent.h rename to Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/WireframeMeshComponent.h index 6ee6536e9097..0c0f613673f8 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/WireframeMeshComponent.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/MeshEditor/Public/WireframeMeshComponent.h @@ -45,7 +45,7 @@ struct FWireframeEdge UCLASS() -class UWireframeMesh : public UObject +class MESHEDITOR_API UWireframeMesh : public UObject { GENERATED_BODY() @@ -95,7 +95,7 @@ public: UCLASS() -class UWireframeMeshComponent : public UMeshComponent +class MESHEDITOR_API UWireframeMeshComponent : public UMeshComponent { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/AssignMaterial.cpp b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/AssignMaterial.cpp new file mode 100644 index 000000000000..cbb6a75702e6 --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/AssignMaterial.cpp @@ -0,0 +1,58 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "AssignMaterial.h" + +#include "ContentBrowserModule.h" +#include "EditableMesh.h" +#include "Framework/Commands/UICommandInfo.h" +#include "IContentBrowserSingleton.h" +#include "IMeshEditorModeUIContract.h" +#include "MeshEditorUtilities.h" +#include "ScopedTransaction.h" + +#define LOCTEXT_NAMESPACE "MeshEditorMode" + +void UAssignMaterialCommand::RegisterUICommand( FBindingContext* BindingContext ) +{ + UI_COMMAND_EXT( BindingContext, /* Out */ UICommandInfo, "AssignMaterial", "Assign Material", "Assigns the highlighted material in the Content Browser to the currently selected polygons.", EUserInterfaceActionType::Button, FInputChord( EKeys::M ) ); +} + +void UAssignMaterialCommand::Execute( IMeshEditorModeEditingContract& MeshEditorMode ) +{ + IContentBrowserSingleton& ContentBrowser = FModuleManager::LoadModuleChecked( "ContentBrowser" ).Get(); + + static TArray SelectedAssets; + ContentBrowser.GetSelectedAssets( SelectedAssets ); + + UMaterialInterface* SelectedMaterial = FAssetData::GetFirstAsset( SelectedAssets ); + + if ( MeshEditorMode.GetActiveAction() != NAME_None ) + { + return; + } + + static TMap< UEditableMesh*, TArray > MeshesAndPolygons; + MeshEditorMode.GetSelectedMeshesAndPolygons( /* Out */ MeshesAndPolygons ); + + if ( MeshesAndPolygons.Num() == 0 ) + { + return; + } + + const FScopedTransaction Transaction( LOCTEXT( "UndoAssignMaterialToPolygon", "Assign Material to Polygon" ) ); + + MeshEditorMode.CommitSelectedMeshes(); + + // Refresh selection (committing may have created a new mesh instance) + MeshEditorMode.GetSelectedMeshesAndPolygons( /* Out */ MeshesAndPolygons ); + for( const auto& MeshAndPolygons : MeshesAndPolygons ) + { + UEditableMesh* EditableMesh = MeshAndPolygons.Key; + + FMeshEditorUtilities::AssignMaterialToPolygons( SelectedMaterial, EditableMesh, MeshAndPolygons.Value ); + + MeshEditorMode.TrackUndo( EditableMesh, EditableMesh->MakeUndo() ); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/DeleteMeshElement.cpp b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/DeleteMeshElement.cpp new file mode 100644 index 000000000000..fb2ba3429a0e --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/DeleteMeshElement.cpp @@ -0,0 +1,133 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DeleteMeshElement.h" + +#include "ContentBrowserModule.h" +#include "EditableMesh.h" +#include "Framework/Commands/UICommandInfo.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "IContentBrowserSingleton.h" +#include "IMeshEditorModeEditingContract.h" +#include "IMeshEditorModeUIContract.h" +#include "ScopedTransaction.h" + +#define LOCTEXT_NAMESPACE "MeshEditorMode" + +void UDeleteMeshElementCommand::RegisterUICommand( FBindingContext* BindingContext ) +{ + UI_COMMAND_EXT( BindingContext, /* Out */ UICommandInfo, "DeleteMeshElement", "Delete", "Delete selected mesh elements, including polygons partly defined by selected elements.", EUserInterfaceActionType::Button, FInputChord( EKeys::Delete ) ); +} + +void UDeleteMeshElementCommand::Execute( IMeshEditorModeEditingContract& MeshEditorMode ) +{ + if( MeshEditorMode.GetActiveAction() != NAME_None ) + { + return; + } + + TMap< UEditableMesh*, TArray< FMeshElement > > MeshesWithElementsToDelete; + MeshEditorMode.GetSelectedMeshesAndElements( EEditableMeshElementType::Any, MeshesWithElementsToDelete ); + if( MeshesWithElementsToDelete.Num() == 0 ) + { + return; + } + + FScopedTransaction Transaction( LOCTEXT( "UndoDeleteMeshElement", "Delete" ) ); + + MeshEditorMode.CommitSelectedMeshes(); + + // Refresh selection (committing may have created a new mesh instance) + MeshEditorMode.GetSelectedMeshesAndElements( EEditableMeshElementType::Any, MeshesWithElementsToDelete ); + + // Deselect the mesh elements before we delete them. This will make sure they become selected again after undo. + MeshEditorMode.DeselectMeshElements( MeshesWithElementsToDelete ); + + for( const auto& MeshAndElements : MeshesWithElementsToDelete ) + { + UEditableMesh* EditableMesh = MeshAndElements.Key; + + EditableMesh->StartModification( EMeshModificationType::Final, EMeshTopologyChange::TopologyChange ); + + for( const FMeshElement& MeshElementToDelete : MeshAndElements.Value ) + { + const bool bDeleteOrphanedEdges = true; + const bool bDeleteOrphanedVertices = true; + const bool bDeleteOrphanedVertexInstances = true; + const bool bDeleteEmptySections = true; + + // If we deleted the same polygon on multiple selected instances of the same mesh, the polygon could already have been deleted + // by the time we get here + if( MeshElementToDelete.IsElementIDValid( EditableMesh ) ) + { + if( MeshElementToDelete.ElementAddress.ElementType == EEditableMeshElementType::Vertex ) + { + EditableMesh->DeleteVertexAndConnectedEdgesAndPolygons( + FVertexID( MeshElementToDelete.ElementAddress.ElementID ), + bDeleteOrphanedEdges, + bDeleteOrphanedVertices, + bDeleteOrphanedVertexInstances, + bDeleteEmptySections ); + + } + else if( MeshElementToDelete.ElementAddress.ElementType == EEditableMeshElementType::Edge ) + { + EditableMesh->DeleteEdgeAndConnectedPolygons( + FEdgeID( MeshElementToDelete.ElementAddress.ElementID ), + bDeleteOrphanedEdges, + bDeleteOrphanedVertices, + bDeleteOrphanedVertexInstances, + bDeleteEmptySections ); + } + else if( MeshElementToDelete.ElementAddress.ElementType == EEditableMeshElementType::Polygon ) + { + static TArray PolygonIDsToDelete; + PolygonIDsToDelete.Reset(); + PolygonIDsToDelete.Add( FPolygonID( MeshElementToDelete.ElementAddress.ElementID ) ); + EditableMesh->DeletePolygons( + PolygonIDsToDelete, + bDeleteOrphanedEdges, + bDeleteOrphanedVertices, + bDeleteOrphanedVertexInstances, + bDeleteEmptySections ); + } + } + } + + EditableMesh->EndModification(); + + MeshEditorMode.TrackUndo( EditableMesh, EditableMesh->MakeUndo() ); + } +} + +void UDeleteMeshElementCommand::AddToVRRadialMenuActionsMenu( IMeshEditorModeUIContract& MeshEditorMode, FMenuBuilder& MenuBuilder, TSharedPtr CommandList, const FName TEMPHACK_StyleSetName, class UVREditorMode* VRMode ) +{ + FSlateIcon DeleteIcon; + switch ( MeshEditorMode.GetMeshElementSelectionMode() ) + { + case EEditableMeshElementType::Vertex: + DeleteIcon = FSlateIcon( TEMPHACK_StyleSetName, "MeshEditorMode.VertexDelete" ); + break; + case EEditableMeshElementType::Edge: + DeleteIcon = FSlateIcon( TEMPHACK_StyleSetName, "MeshEditorMode.EdgeDelete" ); + break; + case EEditableMeshElementType::Polygon: + DeleteIcon = FSlateIcon( TEMPHACK_StyleSetName, "MeshEditorMode.PolyDelete" ); + break; + } + + if ( MeshEditorMode.GetMeshElementSelectionMode() == EEditableMeshElementType::Vertex || + MeshEditorMode.GetMeshElementSelectionMode() == EEditableMeshElementType::Edge || + MeshEditorMode.GetMeshElementSelectionMode() == EEditableMeshElementType::Polygon ) + { + MenuBuilder.AddMenuEntry( + LOCTEXT("Delete", "Delete"), + FText(), + DeleteIcon, + MakeUIAction( MeshEditorMode ), + NAME_None, + EUserInterfaceActionType::CollapsedButton + ); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/FlipPolygon.cpp b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/FlipPolygon.cpp new file mode 100644 index 000000000000..80e719931ea0 --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/FlipPolygon.cpp @@ -0,0 +1,63 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "FlipPolygon.h" + +#include "EditableMesh.h" +#include "Framework/Commands/UICommandInfo.h" +#include "IMeshEditorModeEditingContract.h" +#include "ScopedTransaction.h" + +#define LOCTEXT_NAMESPACE "MeshEditorMode" + +void UFlipPolygonCommand::RegisterUICommand( FBindingContext* BindingContext ) +{ + UI_COMMAND_EXT( BindingContext, /* Out */ UICommandInfo, "FlipPolygon", "Flip", "Flip the currently selected polygons.", EUserInterfaceActionType::Button, FInputChord( EKeys::F, EModifierKey::Shift ) ); +} + +void UFlipPolygonCommand::Execute( IMeshEditorModeEditingContract& MeshEditorMode ) +{ + if ( MeshEditorMode.GetActiveAction() != NAME_None ) + { + return; + } + + static TMap< UEditableMesh*, TArray > MeshesAndPolygons; + MeshEditorMode.GetSelectedMeshesAndPolygons( MeshesAndPolygons ); + + if ( MeshesAndPolygons.Num() == 0 ) + { + return; + } + + const FScopedTransaction Transaction( LOCTEXT( "UndoFlipPolygon", "Flip Polygon" ) ); + + MeshEditorMode.CommitSelectedMeshes(); + + // Refresh selection (committing may have created a new mesh instance) + MeshEditorMode.GetSelectedMeshesAndPolygons( MeshesAndPolygons ); + + // Flip selected polygons + for( const auto& MeshAndPolygons : MeshesAndPolygons ) + { + UEditableMesh* EditableMesh = MeshAndPolygons.Key; + + EditableMesh->StartModification( EMeshModificationType::Final, EMeshTopologyChange::TopologyChange ); + + static TArray PolygonsToFlip; + PolygonsToFlip.Reset(); + + for( const FMeshElement& PolygonElement : MeshAndPolygons.Value ) + { + const FPolygonID PolygonID( PolygonElement.ElementAddress.ElementID ); + PolygonsToFlip.Add( PolygonID ); + } + + EditableMesh->FlipPolygons( PolygonsToFlip ); + + EditableMesh->EndModification(); + + MeshEditorMode.TrackUndo( EditableMesh, EditableMesh->MakeUndo() ); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/AssignMaterial.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/AssignMaterial.h new file mode 100644 index 000000000000..930a644d0a43 --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/AssignMaterial.h @@ -0,0 +1,23 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshEditorCommands.h" + +#include "AssignMaterial.generated.h" + +/** Assigns the highlighted material to the currently selected polygon(s) */ +UCLASS() +class POLYGONMODELING_API UAssignMaterialCommand : public UMeshEditorInstantCommand +{ + GENERATED_BODY() + +public: + + // Overrides + virtual EEditableMeshElementType GetElementType() const override { return EEditableMeshElementType::Polygon; } + + // UMeshEditorCommand overrides + virtual void RegisterUICommand( class FBindingContext* BindingContext ) override; + virtual void Execute( class IMeshEditorModeEditingContract& MeshEditorMode ) override; +}; \ No newline at end of file diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/BevelOrInsetPolygon.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/BevelOrInsetPolygon.h similarity index 96% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/BevelOrInsetPolygon.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/BevelOrInsetPolygon.h index 42a9a1af789a..c3430f9229ec 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/BevelOrInsetPolygon.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/BevelOrInsetPolygon.h @@ -8,7 +8,7 @@ /** Adds a beveled edge to an existing polygon */ UCLASS() -class UBevelPolygonCommand : public UMeshEditorEditCommand +class POLYGONMODELING_API UBevelPolygonCommand : public UMeshEditorEditCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/DeleteMeshElement.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/DeleteMeshElement.h new file mode 100644 index 000000000000..b4a316994cfb --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/DeleteMeshElement.h @@ -0,0 +1,23 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshEditorCommands.h" + +#include "DeleteMeshElement.generated.h" + + +UCLASS() +class POLYGONMODELING_API UDeleteMeshElementCommand : public UMeshEditorInstantCommand +{ + GENERATED_BODY() + +public: + + // Overrides + virtual EEditableMeshElementType GetElementType() const override { return EEditableMeshElementType::Any; } + + virtual void RegisterUICommand( class FBindingContext* BindingContext ) override; + virtual void Execute( class IMeshEditorModeEditingContract& MeshEditorMode ) override; + virtual void AddToVRRadialMenuActionsMenu( class IMeshEditorModeUIContract& MeshEditorMode, class FMenuBuilder& MenuBuilder, TSharedPtr CommandList, const FName TEMPHACK_StyleSetName, class UVREditorMode* VRMode ) override; +}; \ No newline at end of file diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/EditVertexOrEdgeSharpness.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/EditVertexOrEdgeSharpness.h similarity index 95% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/EditVertexOrEdgeSharpness.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/EditVertexOrEdgeSharpness.h index 455d10b9615a..e2a8585424a2 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/EditVertexOrEdgeSharpness.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/EditVertexOrEdgeSharpness.h @@ -8,7 +8,7 @@ /** For subdivision meshes, edits how sharp a vertex corner is by dragging in space */ UCLASS() -class UEditVertexCornerSharpnessCommand : public UMeshEditorEditCommand +class POLYGONMODELING_API UEditVertexCornerSharpnessCommand : public UMeshEditorEditCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtendEdge.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtendEdge.h similarity index 93% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtendEdge.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtendEdge.h index 3daac2c1e9e1..fb77f824e985 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtendEdge.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtendEdge.h @@ -8,7 +8,7 @@ /** Extends an edge by making a copy of it and allowing you to move it around */ UCLASS() -class UExtendEdgeCommand : public UMeshEditorEditCommand +class POLYGONMODELING_API UExtendEdgeCommand : public UMeshEditorEditCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtendVertex.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtendVertex.h similarity index 93% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtendVertex.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtendVertex.h index 83bb34a15cb6..85bf6163abf5 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtendVertex.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtendVertex.h @@ -8,7 +8,7 @@ /** Extend a vertex by making a copy of it, creating new polygons to join the geometry together */ UCLASS() -class UExtendVertexCommand : public UMeshEditorEditCommand +class POLYGONMODELING_API UExtendVertexCommand : public UMeshEditorEditCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtrudePolygon.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtrudePolygon.h similarity index 96% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtrudePolygon.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtrudePolygon.h index 589338d26a9e..a36c99e2cede 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/ExtrudePolygon.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/ExtrudePolygon.h @@ -8,7 +8,7 @@ /** Extrudes the polygon along an axis */ UCLASS() -class UExtrudePolygonCommand : public UMeshEditorEditCommand +class POLYGONMODELING_API UExtrudePolygonCommand : public UMeshEditorEditCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/FlipPolygon.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/FlipPolygon.h new file mode 100644 index 000000000000..1f02951a31a3 --- /dev/null +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/FlipPolygon.h @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "MeshEditorCommands.h" + +#include "FlipPolygon.generated.h" + +UCLASS() +class POLYGONMODELING_API UFlipPolygonCommand : public UMeshEditorInstantCommand +{ + GENERATED_BODY() + +public: + + // Overrides + virtual EEditableMeshElementType GetElementType() const override { return EEditableMeshElementType::Polygon; } + + virtual void RegisterUICommand( class FBindingContext* BindingContext ) override; + virtual void Execute( class IMeshEditorModeEditingContract& MeshEditorMode ) override; +}; \ No newline at end of file diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/HardenOrSoftenEdge.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/HardenOrSoftenEdge.h similarity index 92% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/HardenOrSoftenEdge.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/HardenOrSoftenEdge.h index f33eb34c3ff2..21445cc959df 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/HardenOrSoftenEdge.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/HardenOrSoftenEdge.h @@ -8,7 +8,7 @@ /** Makes an edge hard */ UCLASS() -class UHardenEdgeCommand : public UMeshEditorInstantCommand +class POLYGONMODELING_API UHardenEdgeCommand : public UMeshEditorInstantCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/InsertEdgeLoop.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/InsertEdgeLoop.h similarity index 92% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/InsertEdgeLoop.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/InsertEdgeLoop.h index 2761f3061dd1..4160b519242a 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/InsertEdgeLoop.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/InsertEdgeLoop.h @@ -8,7 +8,7 @@ /** With an edge selected, inserts a loop of edge perpendicular to that edge while dragging */ UCLASS() -class UInsertEdgeLoopCommand : public UMeshEditorEditCommand +class POLYGONMODELING_API UInsertEdgeLoopCommand : public UMeshEditorEditCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/QuadrangulateMesh.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/QuadrangulateMesh.h similarity index 86% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/QuadrangulateMesh.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/QuadrangulateMesh.h index 95ae76acfda9..2ef8b717a298 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/QuadrangulateMesh.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/QuadrangulateMesh.h @@ -8,7 +8,7 @@ /** Quadrangulates the currently selected mesh */ UCLASS() -class UQuadrangulateMeshCommand : public UMeshEditorInstantCommand +class POLYGONMODELING_API UQuadrangulateMeshCommand : public UMeshEditorInstantCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/RemoveEdge.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/RemoveEdge.h similarity index 91% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/RemoveEdge.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/RemoveEdge.h index 322dd285642d..dc8dcd79e9b9 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/RemoveEdge.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/RemoveEdge.h @@ -8,7 +8,7 @@ /** Attempts to remove the selected edge from the polygon, merging the adjacent polygons together */ UCLASS() -class URemoveEdgeCommand : public UMeshEditorInstantCommand +class POLYGONMODELING_API URemoveEdgeCommand : public UMeshEditorInstantCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/RemoveVertex.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/RemoveVertex.h similarity index 91% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/RemoveVertex.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/RemoveVertex.h index 1a9889ea1f45..0eeb123a0edd 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/RemoveVertex.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/RemoveVertex.h @@ -8,7 +8,7 @@ /** Attempts to remove the selected vertex from the polygon it belongs to, keeping the polygon intact */ UCLASS() -class URemoveVertexCommand : public UMeshEditorInstantCommand +class POLYGONMODELING_API URemoveVertexCommand : public UMeshEditorInstantCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/SplitEdge.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/SplitEdge.h similarity index 91% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/SplitEdge.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/SplitEdge.h index b954b2d5918e..e7eeb630a053 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/SplitEdge.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/SplitEdge.h @@ -8,7 +8,7 @@ /** Inserts a vertex along an edge and allows the user to move it around */ UCLASS() -class USplitEdgeCommand : public UMeshEditorEditCommand +class POLYGONMODELING_API USplitEdgeCommand : public UMeshEditorEditCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/SplitPolygon.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/SplitPolygon.h similarity index 96% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/SplitPolygon.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/SplitPolygon.h index d744e0cb711f..f654dd80c139 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/SplitPolygon.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/SplitPolygon.h @@ -8,7 +8,7 @@ /** Base class for polygon splitting */ UCLASS( abstract ) -class USplitPolygonCommand : public UMeshEditorEditCommand +class POLYGONMODELING_API USplitPolygonCommand : public UMeshEditorEditCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/TessellatePolygon.h b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/TessellatePolygon.h similarity index 86% rename from Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/TessellatePolygon.h rename to Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/TessellatePolygon.h index 59b039d099db..c7740215b50f 100644 --- a/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/TessellatePolygon.h +++ b/Engine/Plugins/Editor/MeshEditor/Source/PolygonModeling/Public/TessellatePolygon.h @@ -8,7 +8,7 @@ /** Tessellates selected polygons into smaller polygons */ UCLASS() -class UTessellatePolygonCommand : public UMeshEditorInstantCommand +class POLYGONMODELING_API UTessellatePolygonCommand : public UMeshEditorInstantCommand { GENERATED_BODY() diff --git a/Engine/Plugins/Enterprise/DatasmithContent/DatasmithContent.uplugin b/Engine/Plugins/Enterprise/DatasmithContent/DatasmithContent.uplugin index 7726e636b640..b5323eb2d2be 100644 --- a/Engine/Plugins/Enterprise/DatasmithContent/DatasmithContent.uplugin +++ b/Engine/Plugins/Enterprise/DatasmithContent/DatasmithContent.uplugin @@ -1,8 +1,8 @@ { "FileVersion": 3, - "Version": 180, - "VersionName": "0.18", - "FriendlyName": "DatasmithContent", + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Datasmith Content", "Description": "Content for Datasmith Importer.", "Category": "Unreal Studio", "CreatedBy": "Epic Games, Inc.", diff --git a/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Private/DatasmithImportOptions.cpp b/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Private/DatasmithImportOptions.cpp index e70a3e259f93..a2fe30af1c1e 100644 --- a/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Private/DatasmithImportOptions.cpp +++ b/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Private/DatasmithImportOptions.cpp @@ -109,6 +109,7 @@ void UDatasmithImportOptions::UpdateNotDisplayedConfig( bool bIsAReimport ) FDatasmithStaticMeshImportOptions::FDatasmithStaticMeshImportOptions() : MinLightmapResolution( EDatasmithImportLightmapMin::LIGHTMAP_64 ) , MaxLightmapResolution( EDatasmithImportLightmapMax::LIGHTMAP_512 ) + , bGenerateLightmapUVs( true ) , bRemoveDegenerates( true ) { } diff --git a/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Private/ObjectTemplates/DatasmithStaticMeshTemplate.cpp b/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Private/ObjectTemplates/DatasmithStaticMeshTemplate.cpp index a5c90af0e8ac..3d887b76562a 100644 --- a/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Private/ObjectTemplates/DatasmithStaticMeshTemplate.cpp +++ b/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Private/ObjectTemplates/DatasmithStaticMeshTemplate.cpp @@ -17,9 +17,11 @@ void FDatasmithMeshBuildSettingsTemplate::Apply( FMeshBuildSettings* Destination { DATASMITHOBJECTTEMPLATE_CONDITIONALSET( bUseMikkTSpace, Destination, PreviousTemplate ); - DATASMITHOBJECTTEMPLATE_CONDITIONALSET( bRecomputeNormals, Destination, PreviousTemplate ); + // The settings for RecomputeNormals and RecomputeTangents when True must be honored irrespective of the previous template settings + // because their values are determined by ShouldRecomputeNormals/ShouldRecomputeTangents which determine if they are needed by the renderer + Destination->bRecomputeNormals = PreviousTemplate ? Destination->bRecomputeNormals | bRecomputeNormals : bRecomputeNormals; - DATASMITHOBJECTTEMPLATE_CONDITIONALSET( bRecomputeTangents, Destination, PreviousTemplate ); + Destination->bRecomputeTangents = PreviousTemplate ? Destination->bRecomputeTangents | bRecomputeTangents : bRecomputeTangents; DATASMITHOBJECTTEMPLATE_CONDITIONALSET( bRemoveDegenerates, Destination, PreviousTemplate ); diff --git a/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Public/DatasmithImportOptions.h b/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Public/DatasmithImportOptions.h index 004eb3fd9494..7e6be7bf0039 100644 --- a/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Public/DatasmithImportOptions.h +++ b/Engine/Plugins/Enterprise/DatasmithContent/Source/DatasmithContent/Public/DatasmithImportOptions.h @@ -126,6 +126,9 @@ struct DATASMITHCONTENT_API FDatasmithStaticMeshImportOptions UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = Lightmap) EDatasmithImportLightmapMax MaxLightmapResolution; + UPROPERTY(BlueprintReadWrite, Category = Lightmap) + bool bGenerateLightmapUVs; + UPROPERTY(BlueprintReadWrite, Category = Mesh) bool bRemoveDegenerates; diff --git a/Engine/Plugins/Experimental/PythonScriptPlugin/PythonScriptPlugin.uplugin b/Engine/Plugins/Experimental/PythonScriptPlugin/PythonScriptPlugin.uplugin index 8d6c74f30428..e4976fbf3fbc 100644 --- a/Engine/Plugins/Experimental/PythonScriptPlugin/PythonScriptPlugin.uplugin +++ b/Engine/Plugins/Experimental/PythonScriptPlugin/PythonScriptPlugin.uplugin @@ -7,9 +7,9 @@ "Category" : "Scripting", "CreatedBy" : "Epic Games, Inc.", "CreatedByURL" : "http://epicgames.com", - "DocsURL" : "", + "DocsURL" : "https://docs.unrealengine.com/en-us/Studio/Python", "MarketplaceURL" : "", - "SupportURL" : "", + "SupportURL" : "https://forums.unrealengine.com/", "EnabledByDefault" : false, "CanContainContent" : true, "IsBetaVersion" : true, diff --git a/Engine/Plugins/Experimental/Shotgun/Shotgun.uplugin b/Engine/Plugins/Experimental/Shotgun/Shotgun.uplugin index 3d4070c26dec..eff6a23bda4e 100644 --- a/Engine/Plugins/Experimental/Shotgun/Shotgun.uplugin +++ b/Engine/Plugins/Experimental/Shotgun/Shotgun.uplugin @@ -35,6 +35,10 @@ { "Name": "PythonScriptPlugin", "Enabled": true + }, + { + "Name": "EditorScriptingUtilities", + "Enabled": true } ] } \ No newline at end of file diff --git a/Engine/Plugins/Experimental/Shotgun/Source/Shotgun/Private/ShotgunEngine.cpp b/Engine/Plugins/Experimental/Shotgun/Source/Shotgun/Private/ShotgunEngine.cpp index ea03abcde354..e167d70c5290 100644 --- a/Engine/Plugins/Experimental/Shotgun/Source/Shotgun/Private/ShotgunEngine.cpp +++ b/Engine/Plugins/Experimental/Shotgun/Source/Shotgun/Private/ShotgunEngine.cpp @@ -6,6 +6,7 @@ #include "IAssetRegistry.h" #include "GameFramework/Actor.h" +#include "IPythonScriptPlugin.h" #include "Misc/CoreDelegates.h" #include "Misc/Paths.h" #include "Templates/Casts.h" @@ -35,7 +36,7 @@ static void OnEditorExit() void UShotgunEngine::OnEngineInitialized() const { - FCoreDelegates::OnPreExit.AddStatic(OnEditorExit); + IPythonScriptPlugin::Get()->OnPythonShutdown().AddStatic(OnEditorExit); } void UShotgunEngine::SetSelection(const TArray* InSelectedAssets, const TArray* InSelectedActors) diff --git a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapIdentity/Public/MagicLeapIdentityTypes.h b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapIdentity/Public/MagicLeapIdentityTypes.h index 79170747f11f..c66d23216acb 100644 --- a/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapIdentity/Public/MagicLeapIdentityTypes.h +++ b/Engine/Plugins/Lumin/MagicLeap/Source/MagicLeapIdentity/Public/MagicLeapIdentityTypes.h @@ -7,7 +7,7 @@ #include "MagicLeapIdentityTypes.generated.h" /** Identifies an attribute in a user profile. */ -UENUM(BlueprintType) +UENUM(BlueprintType, meta=(ScriptName="MagicLeapIdentityAttributeType")) enum class EMagicLeapIdentityAttribute : uint8 { UserID, diff --git a/Engine/Plugins/Media/AjaMedia/AjaMedia.uplugin b/Engine/Plugins/Media/AjaMedia/AjaMedia.uplugin index 80e689086444..fa9895502c74 100644 --- a/Engine/Plugins/Media/AjaMedia/AjaMedia.uplugin +++ b/Engine/Plugins/Media/AjaMedia/AjaMedia.uplugin @@ -11,7 +11,7 @@ "Category" : "Media Players", "CanContainContent" : false, "EnabledByDefault" : false, - "IsBetaVersion": false, + "IsBetaVersion": true, "Modules" : [ diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Aja/Aja.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Aja/Aja.cpp index 1b5c59a6c266..021b6285a1b7 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Aja/Aja.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Aja/Aja.cpp @@ -106,59 +106,6 @@ FTimecode FAja::ConvertAJATimecode2Timecode(const AJA::FTimecode& InTimecode, co return FTimecode(InTimecode.Hours, InTimecode.Minutes, InTimecode.Seconds, InTimecode.Frames, FTimecode::IsDropFormatTimecodeSupported(InFPS)); } -namespace AJATimecodeEncoder -{ - void Pattern(FColor* ColorBuffer, uint32 ColorBufferWidth, int32 HeightIndex, int32 Amount) - { - for (int32 Index = 0; Index < Amount; ++Index) - { - *(ColorBuffer + (ColorBufferWidth * HeightIndex) + Index) = (Index % 2) ? FColor::Red : FColor::Black; - } - } - - void Time(FColor* ColorBuffer, uint32 ColorBufferWidth, int32 HeightIndex, int32 Time) - { - int32 Tenth = (Time / 10); - int32 Unit = (Time % 10); - if (Tenth > 0) - { - *(ColorBuffer + (ColorBufferWidth * HeightIndex) + Tenth - 1) = FColor::White; - } - *(ColorBuffer + (ColorBufferWidth * (1 + HeightIndex)) + Unit) = FColor::White; - } -} - -void FAja::EncodeTimecode(const AJA::FTimecode& Timecode, FColor* ColorBuffer, uint32 ColorBufferWidth, uint32 ColorBufferHeight) -{ - const int32 FillWidth = 12; - const int32 FillHeight = 6 * 2; - - if (ColorBufferWidth > FillWidth && ColorBufferHeight > FillHeight) - { - for (int32 IndexHeight = 0; IndexHeight < FillHeight; ++IndexHeight) - { - for (int32 IndexWidth = 0; IndexWidth < FillWidth; ++IndexWidth) - { - *(ColorBuffer + ColorBufferWidth * IndexHeight + IndexWidth) = FColor::Black; - } - } - - AJATimecodeEncoder::Pattern(ColorBuffer, ColorBufferWidth, 0, 2); //hh - AJATimecodeEncoder::Pattern(ColorBuffer, ColorBufferWidth, 1, 10); - AJATimecodeEncoder::Pattern(ColorBuffer, ColorBufferWidth, 3, 6); //mm - AJATimecodeEncoder::Pattern(ColorBuffer, ColorBufferWidth, 4, 10); - AJATimecodeEncoder::Pattern(ColorBuffer, ColorBufferWidth, 6, 6); //ss - AJATimecodeEncoder::Pattern(ColorBuffer, ColorBufferWidth, 7, 10); - AJATimecodeEncoder::Pattern(ColorBuffer, ColorBufferWidth, 9, 6); //ff - AJATimecodeEncoder::Pattern(ColorBuffer, ColorBufferWidth, 10, 10); - - AJATimecodeEncoder::Time(ColorBuffer, ColorBufferWidth, 0, Timecode.Hours); - AJATimecodeEncoder::Time(ColorBuffer, ColorBufferWidth, 3, Timecode.Minutes); - AJATimecodeEncoder::Time(ColorBuffer, ColorBufferWidth, 6, Timecode.Seconds); - AJATimecodeEncoder::Time(ColorBuffer, ColorBufferWidth, 9, Timecode.Frames); - } -} - //~ Log functions implementation //-------------------------------------------------------------------- void FAja::LogInfo(const TCHAR* InFormat, ...) diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Aja/Aja.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Aja/Aja.h index a3a1630bd9a9..67de6043664a 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Aja/Aja.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Aja/Aja.h @@ -22,7 +22,6 @@ public: // Helpers static FTimespan ConvertAJATimecode2Timespan(const AJA::FTimecode& InTimecode, const AJA::FTimecode& PreviousTimeCode, const FTimespan& PreviousTimespan, const FFrameRate& InFPS); static FTimecode ConvertAJATimecode2Timecode(const AJA::FTimecode& InTimecode, const FFrameRate& InFPS); - static void EncodeTimecode(const AJA::FTimecode& Timecode, FColor* ColorBuffer, uint32 ColorBufferWidth, uint32 ColorBufferHeight); private: static void LogInfo(const TCHAR* InFormat, ...); diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/AjaMediaModule.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/AjaMediaModule.cpp index e76cb4422a7e..ce0da57e5950 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/AjaMediaModule.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/AjaMediaModule.cpp @@ -3,6 +3,7 @@ #include "IAjaMediaModule.h" #include "Aja/Aja.h" +#include "AJALib.h" #include "AjaCustomTimeStep.h" #include "AjaMediaPlayer.h" #include "AjaTimecodeProvider.h" @@ -16,6 +17,19 @@ DEFINE_LOG_CATEGORY(LogAjaMedia); #define LOCTEXT_NAMESPACE "AjaMediaModule" +namespace AJAHelpers +{ + FAjaMediaMode FromVideoFormatDescriptor(int32 InDeviceIndex, const AJA::AJAVideoFormats::VideoFormatDescriptor& InDescriptor) + { + FAjaMediaMode Mode; + Mode.DeviceIndex = InDeviceIndex; + Mode.ModeName = InDescriptor.FormatedText; + Mode.VideoFormatIndex = InDescriptor.VideoFormatIndex; + Mode.FrameRate = FFrameRate(InDescriptor.FrameRate * 1001, 1001); + return Mode; + } +} + /** * Implements the NdiMedia module. */ @@ -72,6 +86,15 @@ public: FParse::Value(Cmd, TEXT("Device="), CustomTimeStep->MediaPort.DeviceIndex); FParse::Bool(Cmd, TEXT("EnableOverrunDetection="), CustomTimeStep->bEnableOverrunDetection); + AJA::FAJAVideoFormat VideoFormatIndex = AjaMediaOption::DefaultVideoFormat; + FParse::Value(Cmd, TEXT("VideoFormat="), VideoFormatIndex); + + AJA::AJAVideoFormats::VideoFormatDescriptor Descriptor = AJA::AJAVideoFormats::GetVideoFormat(VideoFormatIndex); + if (Descriptor.bValid) + { + CustomTimeStep->MediaMode = AJAHelpers::FromVideoFormatDescriptor(CustomTimeStep->MediaPort.DeviceIndex, Descriptor); + } + { GEngine->SetCustomTimeStep(CustomTimeStep.Get()); } @@ -97,8 +120,6 @@ public: TimecodeProvider->MediaPort.DeviceIndex = 0; FParse::Value(Cmd, TEXT("Port="), TimecodeProvider->MediaPort.PortIndex); FParse::Value(Cmd, TEXT("Device="), TimecodeProvider->MediaPort.DeviceIndex); - FParse::Value(Cmd, TEXT("Numerator="), TimecodeProvider->FrameRate.Numerator); - FParse::Value(Cmd, TEXT("Denominator="), TimecodeProvider->FrameRate.Denominator); int32 TimecodeFormatInt = 1; if (FParse::Value(Cmd, TEXT("TimecodeFormat="), TimecodeFormatInt)) { @@ -106,6 +127,15 @@ public: TimecodeProvider->TimecodeFormat = (EAjaMediaTimecodeFormat)TimecodeFormatInt; } + AJA::FAJAVideoFormat VideoFormatIndex = AjaMediaOption::DefaultVideoFormat; + FParse::Value(Cmd, TEXT("VideoFormat="), VideoFormatIndex); + + AJA::AJAVideoFormats::VideoFormatDescriptor Descriptor = AJA::AJAVideoFormats::GetVideoFormat(VideoFormatIndex); + if (Descriptor.bValid) + { + CustomTimeStep->MediaMode = AJAHelpers::FromVideoFormatDescriptor(CustomTimeStep->MediaPort.DeviceIndex, Descriptor); + } + GEngine->SetTimecodeProvider(TimecodeProvider.Get()); } else if (FParse::Command(&Cmd, TEXT("Stop"))) diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/AjaMediaPrivate.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/AjaMediaPrivate.h index d24e92206c87..a318df7602f4 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/AjaMediaPrivate.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/AjaMediaPrivate.h @@ -13,6 +13,7 @@ #endif #include "CoreMinimal.h" +#include "AjaMediaFinder.h" #include "AjaMediaSettings.h" DECLARE_LOG_CATEGORY_EXTERN(LogAjaMedia, Log, All); @@ -22,6 +23,7 @@ namespace AjaMediaOption static const FName FrameRateNumerator("FrameRateNumerator"); static const FName FrameRateDenominator("FrameRateDenominator"); static const FName TimecodeFormat("TimecodeFormat"); + static const FName LogDropFrame("LogDropFrame"); static const FName EncodeTimecodeInTexel("EncodeTimecodeInTexel"); static const FName CaptureWithAutoCirculating("CaptureWithAutoCirculating"); static const FName CaptureAncillary1("CaptureAncillary1"); @@ -31,7 +33,14 @@ namespace AjaMediaOption static const FName MaxAncillaryFrameBuffer("MaxAncillaryFrameBuffer"); static const FName AudioChannel("AudioChannel"); static const FName MaxAudioFrameBuffer("MaxAudioFrameBuffer"); - static const FName IsProgressivePicture("IsProgressivePicture"); + static const FName AjaVideoFormat("AjaVideoFormat"); static const FName ColorFormat("ColorFormat"); static const FName MaxVideoFrameBuffer("MaxVideoFrameBuffer"); -} \ No newline at end of file + + static const AJA::FAJAVideoFormat DefaultVideoFormat = 9; // 1080p3000 +} + +namespace AJAHelpers +{ + FAjaMediaMode FromVideoFormatDescriptor(int32 InDeviceIndex, const AJA::AJAVideoFormats::VideoFormatDescriptor& InDescriptor); +} diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaCustomTimeStep.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaCustomTimeStep.cpp index 6f9024a6d2ee..05ad7bfa9f73 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaCustomTimeStep.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaCustomTimeStep.cpp @@ -50,10 +50,11 @@ bool UAjaCustomTimeStep::Initialize(class UEngine* InEngine) State = ECustomTimeStepSynchronizationState::Closed; bDidAValidUpdateTimeStep = false; - if (!MediaPort.IsValid()) + FString FailureReason; + if (!FAjaMediaFinder::IsValid(MediaPort, MediaMode, FailureReason)) { - UE_LOG(LogAjaMedia, Warning, TEXT("The Source of '%s' is not valid."), *GetName()); State = ECustomTimeStepSynchronizationState::Error; + UE_LOG(LogAjaMedia, Warning, TEXT("The CustomTimeStep '%s' is invalid. %s"), *GetName(), *FailureReason); return false; } @@ -66,6 +67,7 @@ bool UAjaCustomTimeStep::Initialize(class UEngine* InEngine) AJA::AJASyncChannelOptions Options(*GetName(), MediaPort.PortIndex); Options.CallbackInterface = SyncCallback; Options.bUseTimecode = false; + Options.VideoFormatIndex = MediaMode.VideoFormatIndex; check(SyncChannel == nullptr); SyncChannel = new AJA::AJASyncChannel(); diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaFinder.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaFinder.cpp index 4fe7e87f729e..3719516039f6 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaFinder.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaFinder.cpp @@ -1,10 +1,13 @@ // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. #include "AjaMediaFinder.h" +#include "AjaMediaPrivate.h" #include "Aja.h" #include "AJALib.h" +#include "Templates/UniquePtr.h" + /* * FAjaMediaSourceId interface */ @@ -49,12 +52,20 @@ FAjaMediaPort::FAjaMediaPort(const FString& InDeviceName, int32 InDeviceIndex, i FString FAjaMediaPort::ToString() const { - return FString::Printf(TEXT("%s [%s]"), *DeviceName, *ToUrl()); + if (IsValid()) + { + return FString::Printf(TEXT("%s [%s]"), *DeviceName, *ToUrl()); + } + return TEXT(""); } FString FAjaMediaPort::ToUrl() const { - return FString::Printf(TEXT("aja://device%d/port%d"), DeviceIndex, (PortIndex)); + if (IsValid()) + { + return FString::Printf(TEXT("aja://device%d/port%d"), DeviceIndex, (PortIndex)); + } + return TEXT("aja://"); } bool FAjaMediaPort::IsValid() const @@ -91,31 +102,22 @@ bool FAjaMediaPort::FromUrl(const FString& Url, bool bDiscoverDeviceName) bool bResult = true; if (bDiscoverDeviceName) { - DeviceName.Reset(); - bResult = FAja::IsInitialized(); if (bResult) { - AJA::FDeviceScanner DeviceScanner = AJA::CreateDeviceScanner(); + TUniquePtr DeviceScanner = MakeUnique(); if (DeviceScanner) { - AJA::DeviceScannerScanHardware(DeviceScanner); - bResult = DeviceScanner != nullptr; + int32 NumDevices = DeviceScanner->GetNumDevices(); + bResult = DeviceIndex < NumDevices; if (bResult) { - uint32 NumDevices = AJA::DeviceScannerGetNumDevices(DeviceScanner); - bResult = (uint32)DeviceIndex < NumDevices; + TCHAR DeviceNameBuffer[AJA::AJADeviceScanner::FormatedTextSize]; + bResult = DeviceScanner->GetDeviceTextId(DeviceIndex, DeviceNameBuffer); if (bResult) { - AJA::FDeviceInfo DeviceInfo = AJA::DeviceScannerGetDeviceInfo(DeviceScanner, PortIndex); - TCHAR DeviceNameBuffer[AjaMediaSourceId::DeviceNameBufferSize]; - AJA::DeviceInfoGetDeviceId(DeviceInfo, DeviceNameBuffer, AjaMediaSourceId::DeviceNameBufferSize); - AJA::ReleaseDeviceInfo(DeviceInfo); - DeviceName = DeviceNameBuffer; } - - AJA::ReleaseDeviceScanner(DeviceScanner); } } } @@ -129,36 +131,30 @@ bool FAjaMediaPort::FromUrl(const FString& Url, bool bDiscoverDeviceName) */ FAjaMediaMode::FAjaMediaMode() - : Mode(INDEX_NONE) -{ -} - -FAjaMediaMode::FAjaMediaMode(const FString& InModeName, int32 InMode) - : ModeName(InModeName) - , Mode(InMode) + : DeviceIndex(INDEX_NONE) + , VideoFormatIndex(INDEX_NONE) { } FString FAjaMediaMode::ToString() const { - return FString::Printf(TEXT("%s [%d]"), *ModeName, Mode); -} - -FString FAjaMediaMode::ToUrl() const -{ - return ToString(); + if (IsValid()) + { + return FString::Printf(TEXT("%s"), *ModeName); + } + return TEXT(""); } bool FAjaMediaMode::IsValid() const { - return Mode != INDEX_NONE; + return VideoFormatIndex != INDEX_NONE; } /* * UAjaMediaFinder interface */ -bool UAjaMediaFinder::GetSources(TArray& OutSources) +bool FAjaMediaFinder::GetSources(TArray& OutSources) { OutSources.Reset(); if (!FAja::IsInitialized()) @@ -166,34 +162,33 @@ bool UAjaMediaFinder::GetSources(TArray& OutSources) return false; } - AJA::FDeviceScanner DeviceScanner = AJA::CreateDeviceScanner(); + TUniquePtr DeviceScanner = MakeUnique(); if (DeviceScanner) { - AJA::DeviceScannerScanHardware(DeviceScanner); - - uint32 NumDevices = AJA::DeviceScannerGetNumDevices(DeviceScanner); - for (uint32 SourceIndex = 0; SourceIndex < NumDevices; ++SourceIndex) + int32 NumDevices = DeviceScanner->GetNumDevices(); + for (int32 SourceIndex = 0; SourceIndex < NumDevices; ++SourceIndex) { - AJA::FDeviceInfo DeviceInfo = AJA::DeviceScannerGetDeviceInfo(DeviceScanner, SourceIndex); - TCHAR DeviceName[AjaMediaSourceId::DeviceNameBufferSize]; - AJA::DeviceInfoGetDeviceId(DeviceInfo, DeviceName, AjaMediaSourceId::DeviceNameBufferSize); - - uint32 InputCount = AJA::DeviceInfoGetVidInputs(DeviceInfo); - for (uint32 Inputs = 0; Inputs < InputCount; ++Inputs) + TCHAR DeviceNameBuffer[AJA::AJADeviceScanner::FormatedTextSize]; + if (DeviceScanner->GetDeviceTextId(SourceIndex, DeviceNameBuffer)) { - OutSources.Add(FAjaMediaPort(DeviceName, SourceIndex, Inputs+1)); + int32 OutputCount; + int32 InputCount; + if (DeviceScanner->GetNumberVideoChannels(SourceIndex, InputCount, OutputCount)) + { + for (int32 Inputs = 0; Inputs < InputCount; ++Inputs) + { + OutSources.Add(FAjaMediaPort(DeviceNameBuffer, SourceIndex, Inputs + 1)); + } + } } - AJA::ReleaseDeviceInfo(DeviceInfo); } - - AJA::ReleaseDeviceScanner(DeviceScanner); } return true; } -bool UAjaMediaFinder::GetModes(TArray& OutModes, bool bInOutput) +bool FAjaMediaFinder::GetModes(int32 DeviceIndex, bool bInOutput, TArray& OutModes) { OutModes.Reset(); if (!FAja::IsInitialized()) @@ -201,14 +196,37 @@ bool UAjaMediaFinder::GetModes(TArray& OutModes, bool bInOutput) return false; } - uint32_t NumModes = AJA::ModeCount(); - TCHAR ModeName[AjaMediaSourceId::ModeNameBufferSize]; - for (uint32_t Mode = 0; Mode < NumModes; ++Mode) + AJA::AJAVideoFormats FrameFormats(DeviceIndex, bInOutput); + + const int32 NumSupportedFormat = FrameFormats.GetNumSupportedFormat(); + OutModes.Reserve(NumSupportedFormat); + for(int32 Index = 0; Index < NumSupportedFormat; ++Index) { - if (AJA::ModeNames(Mode, bInOutput ? AJA::EDirectionFilter::DF_OUTPUT : AJA::EDirectionFilter::DF_INPUT, ModeName, AjaMediaSourceId::ModeNameBufferSize)) - { - OutModes.Add(FAjaMediaMode(ModeName, Mode)); - } + AJA::AJAVideoFormats::VideoFormatDescriptor Descriptor = FrameFormats.GetSupportedFormat(Index); + OutModes.Emplace(AJAHelpers::FromVideoFormatDescriptor(DeviceIndex, Descriptor)); + } + + return true; +} + +bool FAjaMediaFinder::IsValid(const FAjaMediaPort& InPort, const FAjaMediaMode& InMode, FString& OutFailureReason) +{ + if (!InPort.IsValid()) + { + OutFailureReason = TEXT("The MediaPort is invalid."); + return false; + } + + if (!InMode.IsValid()) + { + OutFailureReason = TEXT("The MediaMode is invalid."); + return false; + } + + if (InPort.DeviceIndex != InMode.DeviceIndex) + { + OutFailureReason = TEXT("The MediaPort & MediaMode are not on the same device."); + return false; } return true; diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaOutput.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaOutput.cpp index 12caad32bf18..718c94b82bf5 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaOutput.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaOutput.cpp @@ -6,8 +6,9 @@ /* UAjaMediaOutput *****************************************************************************/ -UAjaMediaOutput::UAjaMediaOutput() - : OutputType(EAjaMediaOutputType::FillOnly) +UAjaMediaOutput::UAjaMediaOutput(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , OutputType(EAjaMediaOutputType::FillOnly) , bOutputWithAutoCirculating(false) , bOutputTimecode(true) , bCopyVideoOnRenderThread(true) @@ -18,6 +19,59 @@ UAjaMediaOutput::UAjaMediaOutput() { } +bool UAjaMediaOutput::Validate(FString& OutFailureReason) const +{ + if (!FillPort.IsValid()) + { + OutFailureReason = FString::Printf(TEXT("The FillPort of '%s' is invalid ."), *GetName()); + return false; + } + + if (OutputReference == EAjaMediaOutputReferenceType::Input) + { + if (!SyncPort.IsValid()) + { + OutFailureReason = FString::Printf(TEXT("The SyncPort of '%s' is invalid ."), *GetName()); + return false; + } + + if (FillPort.DeviceIndex != SyncPort.DeviceIndex) + { + OutFailureReason = FString::Printf(TEXT("The FillPort & SyncPort of '%s' are not on the same device."), *GetName()); + return false; + } + } + + if (OutputType == EAjaMediaOutputType::FillAndKey) + { + if (!KeyPort.IsValid()) + { + OutFailureReason = FString::Printf(TEXT("The KeyPort of '%s' is invalid ."), *GetName()); + return false; + } + + if (FillPort.DeviceIndex != KeyPort.DeviceIndex) + { + OutFailureReason = FString::Printf(TEXT("The FillPort & KeyPort of '%s' are not on the same device."), *GetName()); + return false; + } + } + + if (!MediaMode.IsValid()) + { + OutFailureReason = FString::Printf(TEXT("The MediaMode of '%s' is invalid ."), *GetName()); + return false; + } + + if (MediaMode.DeviceIndex != FillPort.DeviceIndex) + { + OutFailureReason = FString::Printf(TEXT("The MediaMode & FillPort of '%s' are not on the same device."), *GetName()); + return false; + } + + return true; +} + #if WITH_EDITOR bool UAjaMediaOutput::CanEditChange(const UProperty* InProperty) const { @@ -30,6 +84,10 @@ bool UAjaMediaOutput::CanEditChange(const UProperty* InProperty) const { return OutputType == EAjaMediaOutputType::FillAndKey; } + if (InProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UAjaMediaOutput, SyncPort)) + { + return OutputReference == EAjaMediaOutputReferenceType::Input; + } return true; } diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaSource.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaSource.cpp index a6cdb0fedfae..b0a4de290944 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaSource.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaMediaSource.cpp @@ -4,8 +4,7 @@ #include "AjaMediaPrivate.h" UAjaMediaSource::UAjaMediaSource() - : FrameRate(30, 1) - , TimecodeFormat(EAjaMediaTimecodeFormat::None) + : TimecodeFormat(EAjaMediaTimecodeFormat::None) , bCaptureWithAutoCirculating(true) , bCaptureAncillary1(false) , bCaptureAncillary2(false) @@ -14,9 +13,9 @@ UAjaMediaSource::UAjaMediaSource() , MaxNumAncillaryFrameBuffer(8) , AudioChannel(EAjaMediaAudioChannel::Channel8) , MaxNumAudioFrameBuffer(8) - , bIsProgressivePicture(true) , ColorFormat(EAjaMediaColorFormat::BGRA) , MaxNumVideoFrameBuffer(8) + , bLogDropFrame(true) , bEncodeTimecodeInTexel(false) { } @@ -46,9 +45,9 @@ bool UAjaMediaSource::GetMediaOption(const FName& Key, bool DefaultValue) const { return bCaptureVideo; } - if (Key == AjaMediaOption::IsProgressivePicture) + if (Key == AjaMediaOption::LogDropFrame) { - return bIsProgressivePicture; + return bLogDropFrame; } if (Key == AjaMediaOption::EncodeTimecodeInTexel) { @@ -62,11 +61,11 @@ int64 UAjaMediaSource::GetMediaOption(const FName& Key, int64 DefaultValue) cons { if (Key == AjaMediaOption::FrameRateNumerator) { - return FrameRate.Numerator; + return MediaMode.FrameRate.Numerator; } if (Key == AjaMediaOption::FrameRateDenominator) { - return FrameRate.Denominator; + return MediaMode.FrameRate.Denominator; } if (Key == AjaMediaOption::TimecodeFormat) { @@ -84,6 +83,10 @@ int64 UAjaMediaSource::GetMediaOption(const FName& Key, int64 DefaultValue) cons { return MaxNumAudioFrameBuffer; } + if (Key == AjaMediaOption::AjaVideoFormat) + { + return MediaMode.VideoFormatIndex; + } if (Key == AjaMediaOption::ColorFormat) { return (int64)ColorFormat; @@ -109,9 +112,10 @@ bool UAjaMediaSource::HasMediaOption(const FName& Key) const (Key == AjaMediaOption::MaxAncillaryFrameBuffer) || (Key == AjaMediaOption::AudioChannel) || (Key == AjaMediaOption::MaxAudioFrameBuffer) || - (Key == AjaMediaOption::IsProgressivePicture) || + (Key == AjaMediaOption::AjaVideoFormat) || (Key == AjaMediaOption::ColorFormat) || (Key == AjaMediaOption::MaxVideoFrameBuffer) || + (Key == AjaMediaOption::LogDropFrame) || (Key == AjaMediaOption::EncodeTimecodeInTexel) ) { @@ -132,7 +136,14 @@ FString UAjaMediaSource::GetUrl() const bool UAjaMediaSource::Validate() const { - return MediaPort.IsValid(); + FString FailureReason; + if (!FAjaMediaFinder::IsValid(MediaPort, MediaMode, FailureReason)) + { + UE_LOG(LogAjaMedia, Warning, TEXT("The MediaSource '%s' is invalid. %s"), *GetName(), *FailureReason); + return false; + } + + return true; } #if WITH_EDITOR diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaTimecodeProvider.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaTimecodeProvider.cpp index 55ed3987fdf5..a717c619b7e6 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaTimecodeProvider.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Assets/AjaTimecodeProvider.cpp @@ -45,7 +45,7 @@ FTimecode UAjaTimecodeProvider::GetTimecode() const AJA::FTimecode NewTimecode; if (SyncChannel->GetTimecode(NewTimecode)) { - return FAja::ConvertAJATimecode2Timecode(NewTimecode, FrameRate); + return FAja::ConvertAJATimecode2Timecode(NewTimecode, GetFrameRate()); } else if (State == ETimecodeProviderSynchronizationState::Synchronized) { @@ -59,10 +59,11 @@ bool UAjaTimecodeProvider::Initialize(class UEngine* InEngine) { State = ETimecodeProviderSynchronizationState::Closed; - if (!MediaPort.IsValid()) + FString FailureReason; + if (!FAjaMediaFinder::IsValid(MediaPort, MediaMode, FailureReason)) { - UE_LOG(LogAjaMedia, Warning, TEXT("The Source of '%s' is not valid."), *GetName()); State = ETimecodeProviderSynchronizationState::Error; + UE_LOG(LogAjaMedia, Warning, TEXT("The TimecodeProvider '%s' is invalid. %s"), *GetName(), *FailureReason); return false; } diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Player/AjaMediaPlayer.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Player/AjaMediaPlayer.cpp index 4fc6d9be413a..4ad3356c4ffc 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Player/AjaMediaPlayer.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Player/AjaMediaPlayer.cpp @@ -5,6 +5,8 @@ #include "AJA.h" #include "MediaIOCoreSamples.h" +#include "MediaIOCoreEncodeTime.h" + #include "HAL/PlatformAtomics.h" #include "HAL/PlatformProcess.h" @@ -53,6 +55,7 @@ FAjaMediaPlayer::FAjaMediaPlayer(IMediaEventSink& InEventSink) , bUseAncillaryField2(false) , bUseAudio(false) , bUseVideo(false) + , bVerifyFrameDropCount(true) , VideoSampleFormat(EMediaTextureSampleFormat::CharBGRA) , InputChannel(nullptr) , AjaThreadPreviousFrameTimecode() @@ -79,6 +82,11 @@ bool FAjaMediaPlayer::Open(const FString& Url, const IMediaOptions* Options) return false; } + if (!ReadMediaOptions(Options)) + { + return false; + } + AJA::AJADeviceOptions DeviceOptions(DeviceSource.DeviceIndex); // Read options @@ -97,27 +105,29 @@ bool FAjaMediaPlayer::Open(const FString& Url, const IMediaOptions* Options) AjaOptions.bUseTimecode = bUseFrameTimecode; AjaOptions.bUseLTCTimecode = Timecode == EAjaMediaTimecodeFormat::LTC; bEncodeTimecodeInTexel = bUseFrameTimecode && Options->GetMediaOption(AjaMediaOption::EncodeTimecodeInTexel, false); - } { EAjaMediaAudioChannel AudioChannelOption = (EAjaMediaAudioChannel)(Options->GetMediaOption(AjaMediaOption::AudioChannel, (int64)EAjaMediaAudioChannel::Channel8)); AjaOptions.NumberOfAudioChannel = (AudioChannelOption == EAjaMediaAudioChannel::Channel8) ? 8 : 6; } + { + AjaOptions.VideoFormatIndex = Options->GetMediaOption(AjaMediaOption::AjaVideoFormat, (int64)0); + LastVideoFormatIndex = AjaOptions.VideoFormatIndex; + } { EAjaMediaColorFormat ColorFormat = (EAjaMediaColorFormat)(Options->GetMediaOption(AjaMediaOption::ColorFormat, (int64)EMediaTextureSampleFormat::CharBGRA)); VideoSampleFormat = (ColorFormat == EAjaMediaColorFormat::BGRA) ? EMediaTextureSampleFormat::CharBGRA : EMediaTextureSampleFormat::CharUYVY; - AjaOptions.FrameDesc.PixelFormat = (ColorFormat == EAjaMediaColorFormat::BGRA) ? AJA::EPixelFormat::PF_ARGB : AJA::EPixelFormat::PF_UYVY; + AjaOptions.PixelFormat = (ColorFormat == EAjaMediaColorFormat::BGRA) ? AJA::EPixelFormat::PF_ARGB : AJA::EPixelFormat::PF_UYVY; } - { AjaOptions.bUseAncillary = bUseAncillary = Options->GetMediaOption(AjaMediaOption::CaptureAncillary1, false); AjaOptions.bUseAncillaryField2 = bUseAncillaryField2 = Options->GetMediaOption(AjaMediaOption::CaptureAncillary2, false); AjaOptions.bUseAudio = bUseAudio = Options->GetMediaOption(AjaMediaOption::CaptureAudio, false); AjaOptions.bUseVideo = bUseVideo = Options->GetMediaOption(AjaMediaOption::CaptureVideo, true); + AjaOptions.bUseAutoCirculating = Options->GetMediaOption(AjaMediaOption::CaptureWithAutoCirculating, true); } - AjaOptions.bUseAutoCirculating = Options->GetMediaOption(AjaMediaOption::CaptureWithAutoCirculating, true); - AjaOptions.bIsProgressivePicture = Options->GetMediaOption(AjaMediaOption::IsProgressivePicture, true); + bVerifyFrameDropCount = Options->GetMediaOption(AjaMediaOption::LogDropFrame, true); MaxNumAudioFrameBuffer = Options->GetMediaOption(AjaMediaOption::MaxAudioFrameBuffer, (int64)8); MaxNumMetadataFrameBuffer = Options->GetMediaOption(AjaMediaOption::MaxAncillaryFrameBuffer, (int64)8); MaxNumVideoFrameBuffer = Options->GetMediaOption(AjaMediaOption::MaxVideoFrameBuffer, (int64)8); @@ -139,14 +149,14 @@ bool FAjaMediaPlayer::Open(const FString& Url, const IMediaOptions* Options) AudioTrackFormat.SampleRate = 48000; AudioTrackFormat.TypeName = FString(TEXT("PCM")); - VideoFrameRate = FFrameRate(LastFrameInfo.TimeScale, LastFrameInfo.TimeValue); - VideoTrackFormat.Dim = FIntPoint(LastFrameInfo.Width, LastFrameInfo.Height); + AudioTrackFormat.NumChannels = LastAudioChannels; + AudioTrackFormat.SampleRate = LastAudioSampleRate; + + AJA::AJAVideoFormats::VideoFormatDescriptor FrameDescriptor = AJA::AJAVideoFormats::GetVideoFormat(LastVideoFormatIndex); + VideoTrackFormat.Dim = FIntPoint(FrameDescriptor.Width, FrameDescriptor.Height); VideoTrackFormat.FrameRate = VideoFrameRate.AsDecimal(); VideoTrackFormat.FrameRates = TRange(VideoFrameRate.AsDecimal()); - - TCHAR ModeName[AjaMediaPlayerConst::ModeNameBufferSize]; - AJA::FrameDesc2Name(LastFrameDesc, ModeName, AjaMediaPlayerConst::ModeNameBufferSize); - VideoTrackFormat.TypeName = FString(ModeName); + VideoTrackFormat.TypeName = FrameDescriptor.FormatedText; // finalize CurrentState = EMediaState::Preparing; @@ -200,12 +210,7 @@ FString FAjaMediaPlayer::GetStats() const Stats += TEXT("Aja settings\n"); Stats += FString::Printf(TEXT(" Input port: %s\n"), *DeviceSource.ToString()); Stats += FString::Printf(TEXT(" Frame rate: %s\n"), *VideoFrameRate.ToPrettyText().ToString()); - - TCHAR ModeName[AjaMediaPlayerConst::ModeNameBufferSize]; - if (AJA::FrameDesc2Name(LastFrameDesc, ModeName, AjaMediaPlayerConst::ModeNameBufferSize) && bUseVideo) - { - Stats += FString::Printf(TEXT(" Aja Mode: %s\n"), ModeName); - } + Stats += FString::Printf(TEXT(" Aja Mode: %s\n"), *VideoTrackFormat.TypeName); Stats += TEXT("\n\n"); Stats += TEXT("Status\n"); @@ -286,31 +291,6 @@ void FAjaMediaPlayer::TickInput(FTimespan DeltaTime, FTimespan Timecode) return; } - // Its a non atomic read, but its only used for information - // Incomplete read will result in double refresh. - AJA::FFrameDesc FrameDesc = AjaThreadLastFrameDesc; - LastVideoDim = AjaLastVideoDim; - - if (!(LastFrameDesc == FrameDesc)) - { - LastFrameDesc = FrameDesc; - // update the capture format - AJA::FrameDesc2Info(LastFrameDesc, LastFrameInfo); - VideoFrameRate = FFrameRate(LastFrameInfo.TimeScale, LastFrameInfo.TimeValue); - - // From width/height of actual frame - VideoTrackFormat.Dim = LastVideoDim; - VideoTrackFormat.FrameRate = VideoFrameRate.AsDecimal(); - VideoTrackFormat.FrameRates = TRange(VideoFrameRate.AsDecimal()); - - TCHAR ModeName[AjaMediaPlayerConst::ModeNameBufferSize]; - AJA::FrameDesc2Name(LastFrameDesc, ModeName, AjaMediaPlayerConst::ModeNameBufferSize); - VideoTrackFormat.TypeName = FString(ModeName); -} - - AudioTrackFormat.NumChannels = LastAudioChannels; - AudioTrackFormat.SampleRate = LastAudioSampleRate; - if (TickTimeManagement() && !bUseFrameTimecode) { // As default, use the App time @@ -345,10 +325,12 @@ void FAjaMediaPlayer::ProcessFrame() void FAjaMediaPlayer::VerifyFrameDropCount() { + if (bVerifyFrameDropCount) + { uint32 FrameDropCount = AjaThreadFrameDropCount; if (FrameDropCount > LastFrameDropCount) { - UE_LOG(LogAjaMedia, Warning, TEXT("Lost %d frames on Aja input %s. Frame rate is either too slow for the capture card to send the information to UE4."), FrameDropCount - LastFrameDropCount, *DeviceSource.ToString()); + UE_LOG(LogAjaMedia, Warning, TEXT("Lost %d frames on Aja input %s. UE4 frame rate is too slow and the capture card was not able to send the frame(s) to UE4."), FrameDropCount - LastFrameDropCount, *DeviceSource.ToString()); } LastFrameDropCount = FrameDropCount; @@ -370,6 +352,7 @@ void FAjaMediaPlayer::VerifyFrameDropCount() UE_LOG(LogAjaMedia, Warning, TEXT("Lost %d video frames on Aja input %s. Frame rate is either too slow or buffering capacity is too small."), FrameDropCount, *DeviceSource.ToString()); } } +} /* IAJAInputOutputCallbackInterface implementation @@ -461,8 +444,6 @@ bool FAjaMediaPlayer::OnInputFrameReceived(const AJA::AJAInputFrameData& InInput if (bUseVideo && InVideoFrame.VideoBuffer) { - AjaThreadLastFrameDesc = InVideoFrame.FrameDesc; - if (Samples->NumVideoSamples() >= MaxNumVideoFrameBuffer) { FPlatformAtomics::InterlockedIncrement(&AjaThreadAutoCirculateVideoFrameDropCount); @@ -475,7 +456,8 @@ bool FAjaMediaPlayer::OnInputFrameReceived(const AJA::AJAInputFrameData& InInput { if (bEncodeTimecodeInTexel) { - FAja::EncodeTimecode(AjaThreadPreviousFrameTimecode, reinterpret_cast(InVideoFrame.VideoBuffer), InVideoFrame.Width, InVideoFrame.Height); + FMediaIOCoreEncodeTime EncodeTime(VideoSampleFormat, reinterpret_cast(InVideoFrame.VideoBuffer), InVideoFrame.Width, InVideoFrame.Height); + EncodeTime.Render(0, 0, AjaThreadPreviousFrameTimecode.Hours, AjaThreadPreviousFrameTimecode.Minutes, AjaThreadPreviousFrameTimecode.Seconds, AjaThreadPreviousFrameTimecode.Frames); } if (TextureSample->InitializeProgressive(InVideoFrame, VideoSampleFormat, AjaThreadCurrentTime)) diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Player/AjaMediaPlayer.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Player/AjaMediaPlayer.h index 4691d48da758..7fa39c3f557b 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Player/AjaMediaPlayer.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Private/Player/AjaMediaPlayer.h @@ -134,6 +134,7 @@ private: bool bUseAncillaryField2; bool bUseAudio; bool bUseVideo; + bool bVerifyFrameDropCount; /** The current video sample format. */ EMediaTextureSampleFormat VideoSampleFormat; @@ -145,11 +146,7 @@ private: AJA::AJAInputChannel* InputChannel; /** Frame Description from capture device */ - AJA::FFrameDesc LastFrameDesc; - AJA::FFrameDesc AjaThreadLastFrameDesc; - - /** Information about the frame desc */ - AJA::FFrameInfo LastFrameInfo; + AJA::FAJAVideoFormat LastVideoFormatIndex; /** Previous frame timecode to calculate a timespan */ AJA::FTimecode AjaThreadPreviousFrameTimecode; diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaCustomTimeStep.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaCustomTimeStep.h index 3b5bf9601e98..d8648aa81179 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaCustomTimeStep.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaCustomTimeStep.h @@ -21,7 +21,7 @@ /** * Control the Engine TimeStep via the AJA card */ -UCLASS(editinlinenew, meta=(DisplayName="AJA SDI Input")) +UCLASS(Blueprintable, editinlinenew, meta=(DisplayName="AJA SDI Input")) class AJAMEDIA_API UAjaCustomTimeStep : public UFixedFrameRateCustomTimeStep { GENERATED_UCLASS_BODY() @@ -50,6 +50,10 @@ public: UPROPERTY(EditAnywhere, Category="Genlock options", AssetRegistrySearchable, meta=(DisplayName="Source")) FAjaMediaPort MediaPort; + /** The expected signal input from the MediaPort. */ + UPROPERTY(EditAnywhere, Category="Genlock options", meta=(MediaPort="MediaPort")) + FAjaMediaMode MediaMode; + /** Enable mechanism to detect Engine loop overrunning the source */ UPROPERTY(EditAnywhere, Category="Genlock options", meta=(DisplayName="Display Dropped Frames Warning")) bool bEnableOverrunDetection; diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaFinder.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaFinder.h index b59efc7a1f7e..03ae20185d2f 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaFinder.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaFinder.h @@ -4,8 +4,9 @@ #include "CoreMinimal.h" -#include "AjaMediaFinder.generated.h" +#include "Misc/FrameRate.h" +#include "AjaMediaFinder.generated.h" /** * Identifies an Aja media source. @@ -39,6 +40,7 @@ public: int32 PortIndex; public: + bool operator==(const FAjaMediaPort& Other) const { return Other.DeviceIndex == DeviceIndex && Other.PortIndex == PortIndex; } /** * Get a string representation of this source. @@ -76,74 +78,52 @@ public: /** Default constructor. */ FAjaMediaMode(); - /** - * Create and initialize a new instance. - */ - FAjaMediaMode(const FString& InModeName, int32 inMode); +public: + /** The index of the Aja Device */ + UPROPERTY() + int32 DeviceIndex; - /** The retail name of the Device, i.e. "IoExpress". */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AJA) + /** The name of the mode, i.e. "1080p 60". */ + UPROPERTY() FString ModeName; - /** The index of the Device */ - UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = AJA, meta = (ClampMin = "0")) - int32 Mode; + /** The frame rate of the mode */ + UPROPERTY() + FFrameRate FrameRate; + + /** The video format index for AJA */ + UPROPERTY() + int32 VideoFormatIndex; public: + bool operator==(const FAjaMediaMode& Other) const + { + return Other.DeviceIndex == DeviceIndex && Other.FrameRate == FrameRate && Other.VideoFormatIndex == VideoFormatIndex; + } /** * Get a string representation of this mode. - * @return String representation, i.e. "". + * @return i.e. "1080p 60". */ FString ToString() const; - /** - * Get a url used by the Media framework - * @return Url representation, "aja://device0/port1" - */ - FString ToUrl() const; - - /** Return true if the device & port index have been set properly */ + /** Return true if the MediaMode has been set properly */ bool IsValid() const; }; -/** Used to manage input modes. */ -USTRUCT(BlueprintType) -struct AJAMEDIA_API FAjaMediaModeInput : public FAjaMediaMode -{ - GENERATED_BODY() -}; - -/** Used to manage output modes. */ -USTRUCT(BlueprintType) -struct AJAMEDIA_API FAjaMediaModeOutput : public FAjaMediaMode -{ - GENERATED_BODY() -}; - /* * Find all of the AJA Inputs */ -UCLASS() -class AJAMEDIA_API UAjaMediaFinder : public UObject +class AJAMEDIA_API FAjaMediaFinder { - GENERATED_BODY() - public: - /** - * Get the list of AJA media sources installed in the machine. - * @param OutSources Will contain the collection of found NDI source names and their URLs. - * @return true on success, false if the finder wasn't initialized. - */ - UFUNCTION(BlueprintCallable, Category=AJA) + /** Get the list of AJA device installed in the machine. */ static bool GetSources(TArray& OutSources); - /** - * Get the list of Supported AJA video modes. - * @param OutModes Will contain the collection of found modes. - * @return true on success, false if the finder wasn't initialized. - */ - UFUNCTION(BlueprintCallable, Category = AJA) - static bool GetModes(TArray& OutModes, bool bInOutput); + /** Get the list of Supported AJA video modes. */ + static bool GetModes(int32 DeviceIndex, bool bInOutput, TArray& OutModes); + + /** Return true if the device & port index have been set properly */ + static bool IsValid(const FAjaMediaPort& InPort, const FAjaMediaMode& InMode, FString& OutFailureReason); }; diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaOutput.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaOutput.h index 8d73b57bdfe6..89ef78cad06e 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaOutput.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaOutput.h @@ -17,6 +17,14 @@ enum class EAjaMediaOutputType : uint8 FillAndKey UMETA(Tooltip="Fill will be on provided FillPort and Key will be on KeyPort"), }; +UENUM() +enum class EAjaMediaOutputReferenceType +{ + FreeRun, + External, + Input +}; + /** * Output Media for Aja streams. * The output format is ARGB8. @@ -24,9 +32,7 @@ enum class EAjaMediaOutputType : uint8 UCLASS(BlueprintType) class AJAMEDIA_API UAjaMediaOutput : public UObject { - GENERATED_BODY() - - UAjaMediaOutput(); + GENERATED_UCLASS_BODY() public: /** @@ -36,11 +42,9 @@ public: UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA") EAjaMediaOutputType OutputType; - /** - * Frame format. - */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = AJA, AssetRegistrySearchable) - FAjaMediaModeOutput MediaMode; + /** The signal output mode. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = AJA, AssetRegistrySearchable, meta=(CustomizeAsInput="false", MediaPort="FillPort")) + FAjaMediaMode MediaMode; /** * The AJA Device and port to output to. @@ -49,14 +53,6 @@ public: UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA", AssetRegistrySearchable) FAjaMediaPort FillPort; - /** - * The AJA Device and port to sync with. - * Need to be the same Device as the FillPort. - * This combines the device ID, and the output port. - */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA") - FAjaMediaPort SyncPort; - /** * The AJA Device and port to output the key to. * Need to be the same Device as the FillPort. @@ -67,6 +63,19 @@ public: UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA", meta=(EditCondition="IN_CPP")) FAjaMediaPort KeyPort; + + /** The AJA Device output sync with either its internal clock, an external reference, or an other input. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA") + EAjaMediaOutputReferenceType OutputReference; + + /** + * The AJA Device and port to sync with. + * Need to be the same Device as the FillPort. + * This combines the device ID, and the output port. + */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA", meta=(EditCondition="IN_CPP")) + FAjaMediaPort SyncPort; + public: /** * The output of the Audio, Ancillary and/or video will be perform at the same time. @@ -116,6 +125,8 @@ public: bool bEncodeTimecodeInTexel; public: + bool Validate(FString& FailureReason) const; + #if WITH_EDITOR virtual bool CanEditChange(const UProperty* InProperty) const override; virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaSource.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaSource.h index 17416357e097..111025ce88fd 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaSource.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaMediaSource.h @@ -59,9 +59,9 @@ public: UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA", AssetRegistrySearchable) FAjaMediaPort MediaPort; - /** Source FrameRate(default = 30). */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA") - FFrameRate FrameRate; + /** The expected signal input from the MediaPort. */ + UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA", meta=(MediaPort="MediaPort")) + FAjaMediaMode MediaMode; /** Use the time code embedded in the input stream. */ UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="AJA") @@ -118,13 +118,6 @@ public: int32 MaxNumAudioFrameBuffer; public: - /** - * Specifies if the video format is expected to be progressive or not. - * The hardware has no way of knowing if the incoming signal is progressive or interlaced (e.g., 525/29.97fps progressive versus 525/59.94fps interlaced) - */ - UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Video", meta=(EditCondition="bCaptureVideo")) - bool bIsProgressivePicture; - /** Desired color format of input video frames (default = BGRA). */ UPROPERTY(BlueprintReadWrite, EditAnywhere, Category="Video", meta=(EditCondition="bCaptureVideo")) EAjaMediaColorFormat ColorFormat; @@ -134,6 +127,10 @@ public: int32 MaxNumVideoFrameBuffer; public: + /** Log a warning when there's a drop frame. */ + UPROPERTY(EditAnywhere, Category="Debug") + bool bLogDropFrame; + /** * Encode Timecode in the output * Current value will be white. The format will be encoded in hh:mm::ss::ff. Each value, will be on a different line. @@ -154,6 +151,7 @@ public: virtual FString GetUrl() const override; virtual bool Validate() const override; +public: #if WITH_EDITOR virtual bool CanEditChange(const UProperty* InProperty) const override; virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaTimecodeProvider.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaTimecodeProvider.h index 6d018a41d822..f19da1148ed9 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaTimecodeProvider.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMedia/Public/AjaTimecodeProvider.h @@ -17,7 +17,7 @@ namespace AJA /** * Class to fetch a timecode via an AJA card */ -UCLASS(editinlinenew, meta=(DisplayName="AJA SDI Input")) +UCLASS(Blueprintable, editinlinenew, meta=(DisplayName="AJA SDI Input")) class AJAMEDIA_API UAjaTimecodeProvider : public UTimecodeProvider { GENERATED_UCLASS_BODY() @@ -25,7 +25,7 @@ class AJAMEDIA_API UAjaTimecodeProvider : public UTimecodeProvider public: //~ UTimecodeProvider interface virtual FTimecode GetTimecode() const override; - virtual FFrameRate GetFrameRate() const override { return FrameRate; } + virtual FFrameRate GetFrameRate() const override { return MediaMode.FrameRate; } virtual ETimecodeProviderSynchronizationState GetSynchronizationState() const override { return State; } virtual bool Initialize(class UEngine* InEngine) override; virtual void Shutdown(class UEngine* InEngine) override; @@ -44,14 +44,14 @@ public: UPROPERTY(EditAnywhere, Category="Timecode options", AssetRegistrySearchable, meta=(DisplayName="Source")) FAjaMediaPort MediaPort; + /** The expected signal input from the MediaPort. */ + UPROPERTY(EditAnywhere, Category="Timecode options", meta=(MediaPort="MediaPort")) + FAjaMediaMode MediaMode; + /** The type of Timecode to read from SDI stream. */ UPROPERTY(EditAnywhere, Category="Timecode options") EAjaMediaTimecodeFormat TimecodeFormat; - /** Frame rate expected from the SDI stream. */ - UPROPERTY(EditAnywhere, Category="Timecode options") - FFrameRate FrameRate; - private: /** AJA Port to capture the Sync */ diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/AjaMediaEditor.Build.cs b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/AjaMediaEditor.Build.cs index fb95701acf35..82e22234c0c1 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/AjaMediaEditor.Build.cs +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/AjaMediaEditor.Build.cs @@ -13,6 +13,7 @@ namespace UnrealBuildTool.Rules "Core", "CoreUObject", "MediaAssets", + "Projects", "PropertyEditor", "Settings", "Slate", @@ -22,6 +23,7 @@ namespace UnrealBuildTool.Rules PrivateIncludePathModuleNames.AddRange( new string[] { + "AJA", "AssetTools", }); diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/AjaMediaEditorModule.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/AjaMediaEditorModule.cpp index 2a220dd294d3..3f62798b191c 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/AjaMediaEditorModule.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/AjaMediaEditorModule.cpp @@ -8,11 +8,16 @@ #include "Customizations/AjaMediaPortCustomization.h" #include "Customizations/AjaMediaModeCustomization.h" +#include "Brushes/SlateImageBrush.h" +#include "Interfaces/IPluginManager.h" #include "ISettingsModule.h" #include "ISettingsSection.h" #include "Modules/ModuleInterface.h" #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" +#include "Styling/SlateStyle.h" +#include "Styling/SlateStyleRegistry.h" +#include "Templates/UniquePtr.h" #define LOCTEXT_NAMESPACE "AjaMediaEditor" @@ -28,26 +33,29 @@ public: virtual void StartupModule() override { RegisterCustomizations(); + RegisterStyle(); } virtual void ShutdownModule() override { if (!UObjectInitialized() && !GIsRequestingExit) { + UnregisterStyle(); UnregisterCustomizations(); } } -protected: +private: + TUniquePtr StyleInstance; + +private: /** Register details view customizations. */ void RegisterCustomizations() { FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.RegisterCustomPropertyTypeLayout(FAjaMediaPort::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAjaMediaPortCustomization::MakeInstance)); - - PropertyModule.RegisterCustomPropertyTypeLayout(FAjaMediaModeInput::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAjaMediaModeCustomization::MakeInputInstance)); - PropertyModule.RegisterCustomPropertyTypeLayout(FAjaMediaModeOutput::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAjaMediaModeCustomization::MakeOutputInstance)); + PropertyModule.RegisterCustomPropertyTypeLayout(FAjaMediaMode::StaticStruct()->GetFName(), FOnGetPropertyTypeCustomizationInstance::CreateStatic(&FAjaMediaModeCustomization::MakeInstance)); } /** Unregister details view customizations. */ @@ -55,11 +63,39 @@ protected: { FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked("PropertyEditor"); PropertyModule.UnregisterCustomPropertyTypeLayout(FAjaMediaPort::StaticStruct()->GetFName()); - - PropertyModule.UnregisterCustomPropertyTypeLayout(FAjaMediaModeInput::StaticStruct()->GetFName()); - PropertyModule.UnregisterCustomPropertyTypeLayout(FAjaMediaModeOutput::StaticStruct()->GetFName()); + PropertyModule.UnregisterCustomPropertyTypeLayout(FAjaMediaMode::StaticStruct()->GetFName()); } + void RegisterStyle() + { +#define IMAGE_BRUSH(RelativePath, ...) FSlateImageBrush(StyleInstance->RootToContentDir(RelativePath, TEXT(".png")), __VA_ARGS__) + + StyleInstance = MakeUnique("AjaMediaStyle"); + + TSharedPtr Plugin = IPluginManager::Get().FindPlugin(TEXT("AjaMedia")); + if (Plugin.IsValid()) + { + StyleInstance->SetContentRoot(FPaths::Combine(Plugin->GetContentDir(), TEXT("Editor/Icons"))); + } + + const FVector2D Icon20x20(20.0f, 20.0f); + const FVector2D Icon64x64(64.0f, 64.0f); + + StyleInstance->Set("ClassThumbnail.AjaMediaSource", new IMAGE_BRUSH("AjaMediaSource_64x", Icon64x64)); + StyleInstance->Set("ClassIcon.AjaMediaSource", new IMAGE_BRUSH("AjaMediaSource_20x", Icon20x20)); + StyleInstance->Set("ClassThumbnail.AjaMediaOutput", new IMAGE_BRUSH("AjaMediaOutput_64x", Icon64x64)); + StyleInstance->Set("ClassIcon.AjaMediaOutput", new IMAGE_BRUSH("AjaMediaOutput_20x", Icon20x20)); + + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance.Get()); + +#undef IMAGE_BRUSH + } + + void UnregisterStyle() + { + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance.Get()); + StyleInstance.Reset(); + } }; diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaModeCustomization.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaModeCustomization.cpp index 37505abfdbd4..58c81670af4c 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaModeCustomization.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaModeCustomization.cpp @@ -11,22 +11,38 @@ #define LOCTEXT_NAMESPACE "AjaMediaPortCustomization" -FAjaMediaModeCustomization::FAjaMediaModeCustomization(bool InOutput) - : bOutput(InOutput) -{ -} - void FAjaMediaModeCustomization::CustomizeHeader(TSharedRef InPropertyHandle, FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& CustomizationUtils) { MediaModeProperty = InPropertyHandle; + MediaPortProperty.Reset(); + if (MediaModeProperty->GetNumPerObjectValues() == 1 && MediaModeProperty->IsValidHandle()) { UProperty* Property = MediaModeProperty->GetProperty(); check(Property && Cast(Property) && Cast(Property)->Struct && - (Cast(Property)->Struct->IsChildOf(FAjaMediaModeInput::StaticStruct()) - || Cast(Property)->Struct->IsChildOf(FAjaMediaModeOutput::StaticStruct()))); + Cast(Property)->Struct->IsChildOf(FAjaMediaMode::StaticStruct())); + + // Check if it's an output. By default it's an Input + { + static FName NAME_CustomizeAsInput = TEXT("CustomizeAsInput"); + const FString& MetaDataValue = Property->GetMetaData(NAME_CustomizeAsInput); + bOutput = Property->HasMetaData(NAME_CustomizeAsInput) && !MetaDataValue.IsEmpty() && !MetaDataValue.ToBool(); + } + // Get the MediaPort to read from the same device + { + static FName NAME_MediaPort = TEXT("MediaPort"); + TSharedPtr ParentHandle = InPropertyHandle->GetParentHandle(); + if (ParentHandle.IsValid() && Property->HasMetaData(NAME_MediaPort)) + { + const FString& MetaDataValue = Property->GetMetaData(NAME_MediaPort); + if (!MetaDataValue.IsEmpty()) + { + MediaPortProperty = ParentHandle->GetChildHandle(*MetaDataValue, false); + } + } + } TArray RawData; MediaModeProperty->AccessRawData(RawData); @@ -52,7 +68,7 @@ void FAjaMediaModeCustomization::CustomizeHeader(TSharedRef InP .VAlign(VAlign_Center) [ SNew(STextBlock) - .Text(TAttribute::Create(TAttribute::FGetter::CreateLambda([=] { return FText::FromString(MediaModeValue->ToUrl()); }))) + .Text(MakeAttributeLambda([=] { return FText::FromString(MediaModeValue->ToString()); })) ] + SHorizontalBox::Slot() .AutoWidth() @@ -63,7 +79,7 @@ void FAjaMediaModeCustomization::CustomizeHeader(TSharedRef InP .OnGetMenuContent(this, &FAjaMediaModeCustomization::HandleSourceComboButtonMenuContent) .ContentPadding(FMargin(4.0, 2.0)) ] - ].IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda([=] { return !InPropertyHandle->IsEditConst() && PropertyUtils->IsPropertyEditingEnabled(); }))); + ].IsEnabled(MakeAttributeLambda([=] { return !InPropertyHandle->IsEditConst() && PropertyUtils->IsPropertyEditingEnabled(); })); } } @@ -73,9 +89,25 @@ void FAjaMediaModeCustomization::CustomizeChildren(TSharedRef I TSharedRef FAjaMediaModeCustomization::HandleSourceComboButtonMenuContent() const { + int32 DeviceIndex = 0; + if (MediaPortProperty.IsValid()) + { + TArray RawData; + MediaPortProperty->AccessRawData(RawData); + + check(RawData.Num() == 1); + FAjaMediaPort* MediaPortValue = reinterpret_cast(RawData[0]); + check(MediaPortValue); + if (!MediaPortValue->IsValid()) + { + return SNullWidget::NullWidget; + } + DeviceIndex = MediaPortValue->DeviceIndex; + } + // fetch found sources TArray OutModes; - if (!UAjaMediaFinder::GetModes(OutModes, bOutput)) + if (!FAjaMediaFinder::GetModes(DeviceIndex, bOutput, OutModes)) { return SNullWidget::NullWidget; } @@ -93,7 +125,7 @@ TSharedRef FAjaMediaModeCustomization::HandleSourceComboButtonMenuConte for (const FAjaMediaMode& Mode : OutModes) { const TSharedPtr ValueProperty = MediaModeProperty; - const FString Url = Mode.ToUrl(); + const FString Url = Mode.ToString(); MenuBuilder.AddMenuEntry( FText::FromString(Mode.ToString()), @@ -101,21 +133,23 @@ TSharedRef FAjaMediaModeCustomization::HandleSourceComboButtonMenuConte FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=] { + if (UStructProperty* StructProperty = Cast(MediaModeProperty->GetProperty())) + { + TArray RawData; + MediaModeProperty->AccessRawData(RawData); + FAjaMediaMode* PreviousMediaModeValue = reinterpret_cast(RawData[0]); - TArray RawData; - MediaModeProperty->AccessRawData(RawData); - - check(RawData.Num() == 1); - MediaModeProperty->NotifyPreChange(); - FAjaMediaMode* MediaModeValue = reinterpret_cast(RawData[0]); - *MediaModeValue = Mode; - MediaModeProperty->NotifyPostChange(); - MediaModeProperty->NotifyFinishedChangingProperties(); + FString TextValue; + StructProperty->Struct->ExportText(TextValue, &Mode, PreviousMediaModeValue, nullptr, EPropertyPortFlags::PPF_None, nullptr); + ensure(MediaModeProperty->SetValueFromFormattedString(TextValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Result::Success); + } }), FCanExecuteAction(), FIsActionChecked::CreateLambda([=] { - FString CurrentValue; - return ((ValueProperty->GetValue(CurrentValue) == FPropertyAccess::Success) && CurrentValue == Url); + TArray RawData; + MediaModeProperty->AccessRawData(RawData); + FAjaMediaMode* MediaModeValue = reinterpret_cast(RawData[0]); + return *MediaModeValue == Mode; }) ), NAME_None, diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaModeCustomization.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaModeCustomization.h index dc1fb6a779b0..86758d3f232b 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaModeCustomization.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaModeCustomization.h @@ -12,18 +12,11 @@ class FAjaMediaModeCustomization : public IPropertyTypeCustomization { public: - FAjaMediaModeCustomization(bool InOutput = false); - - static TSharedRef MakeInputInstance() + static TSharedRef MakeInstance() { return MakeShareable(new FAjaMediaModeCustomization()); } - static TSharedRef MakeOutputInstance() - { - return MakeShareable(new FAjaMediaModeCustomization(true)); - } - /** IPropertyTypeCustomization interface */ virtual void CustomizeHeader(TSharedRef InPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& PropertyTypeCustomizationUtils) override; virtual void CustomizeChildren(TSharedRef InPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& PropertyTypeCustomizationUtils) override; @@ -34,6 +27,9 @@ private: /** Direction filter */ bool bOutput; - /** Pointer to the MediaPort property handle. */ + /** Pointer to the MediaMode property handle. */ TSharedPtr MediaModeProperty; + + /** Pointer to the MediaPort property handle. */ + TSharedPtr MediaPortProperty; }; diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaPortCustomization.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaPortCustomization.cpp index 5d1374279b46..5cbffaeb85ac 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaPortCustomization.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaEditor/Private/Customizations/AjaMediaPortCustomization.cpp @@ -43,7 +43,7 @@ void FAjaMediaPortCustomization::CustomizeHeader(TSharedRef InP .VAlign(VAlign_Center) [ SNew(STextBlock) - .Text(TAttribute::Create(TAttribute::FGetter::CreateLambda([=] { return FText::FromString(MediaPortValue->ToUrl()); }))) + .Text(MakeAttributeLambda([=] { return FText::FromString(MediaPortValue->ToUrl()); })) ] + SHorizontalBox::Slot() .AutoWidth() @@ -54,7 +54,7 @@ void FAjaMediaPortCustomization::CustomizeHeader(TSharedRef InP .OnGetMenuContent(this, &FAjaMediaPortCustomization::HandleSourceComboButtonMenuContent) .ContentPadding(FMargin(4.0, 2.0)) ] - ].IsEnabled(TAttribute::Create(TAttribute::FGetter::CreateLambda([=] { return !InPropertyHandle->IsEditConst() && PropertyUtils->IsPropertyEditingEnabled(); }))); + ].IsEnabled(MakeAttributeLambda([=] { return !InPropertyHandle->IsEditConst() && PropertyUtils->IsPropertyEditingEnabled(); })); } } @@ -66,7 +66,7 @@ TSharedRef FAjaMediaPortCustomization::HandleSourceComboButtonMenuConte { // fetch found Aja sources TArray OutSources; - if (!UAjaMediaFinder::GetSources(OutSources)) + if (!FAjaMediaFinder::GetSources(OutSources)) { return SNullWidget::NullWidget; } @@ -89,21 +89,23 @@ TSharedRef FAjaMediaPortCustomization::HandleSourceComboButtonMenuConte FSlateIcon(), FUIAction( FExecuteAction::CreateLambda([=] { + if (UStructProperty* StructProperty = Cast(MediaPortProperty->GetProperty())) + { + TArray RawData; + MediaPortProperty->AccessRawData(RawData); + FAjaMediaPort* PreviousMediaPortValue = reinterpret_cast(RawData[0]); - TArray RawData; - MediaPortProperty->AccessRawData(RawData); - - check(RawData.Num() == 1); - MediaPortProperty->NotifyPreChange(); - FAjaMediaPort* MediaPortValue = reinterpret_cast(RawData[0]); - *MediaPortValue = Source; - MediaPortProperty->NotifyPostChange(); - MediaPortProperty->NotifyFinishedChangingProperties(); + FString TextValue; + StructProperty->Struct->ExportText(TextValue, &Source, PreviousMediaPortValue, nullptr, EPropertyPortFlags::PPF_None, nullptr); + ensure(MediaPortProperty->SetValueFromFormattedString(TextValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Result::Success); + } }), FCanExecuteAction(), FIsActionChecked::CreateLambda([=] { - FString CurrentValue; - return ((ValueProperty->GetValue(CurrentValue) == FPropertyAccess::Success) && CurrentValue == Url); + TArray RawData; + MediaPortProperty->AccessRawData(RawData); + FAjaMediaPort* MediaPortValue = reinterpret_cast(RawData[0]); + return *MediaPortValue == Source; }) ), NAME_None, diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/AjaMediaOutput.Build.cs b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/AjaMediaOutput.Build.cs index 77fdc9d92da5..e1614834515b 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/AjaMediaOutput.Build.cs +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/AjaMediaOutput.Build.cs @@ -25,7 +25,8 @@ namespace UnrealBuildTool.Rules "Core", "CoreUObject", "Engine", - "MovieSceneCapture", + "MediaIOCore", + "MovieSceneCapture", "RenderCore", "RHI", "Slate", diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutput.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutput.cpp index 1a20693d70c9..23c0581398c7 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutput.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutput.cpp @@ -63,7 +63,7 @@ void UAjaMediaViewportOutput::ActivateOutput(UAjaMediaOutput* MediaOutput) if (MediaOutput == nullptr) { - UE_LOG(LogAjaMediaOutput, Error, TEXT("Couldn't start the capture. No Media Output was provided.")); + UE_LOG(LogAjaMediaOutput, Error, TEXT("Couldn't start the capture. The Media Output is invalid.")); return; } diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutputImpl.cpp b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutputImpl.cpp index 36478065cde5..28c909c28390 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutputImpl.cpp +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutputImpl.cpp @@ -2,9 +2,9 @@ #include "AjaMediaViewportOutputImpl.h" - #include "AjaMediaOutput.h" #include "IAjaMediaOutputModule.h" +#include "MediaIOCoreEncodeTime.h" #include "RHIResources.h" @@ -53,7 +53,7 @@ FAjaMediaViewportOutputImpl::FAjaMediaViewportOutputImpl() , bIgnoreTextureAlphaChanged(false) , WakeUpEvent(nullptr) , CurrentState(EMediaState::Closed) - , AjaThreadNewState(EMediaState::Error) + , AjaThreadNewState(EMediaState::Closed) , OutputChannel(nullptr) , LastFrameDropCount(0) , FrameRate(30, 1) @@ -166,7 +166,7 @@ void FAjaMediaViewportOutputImpl::Shutdown() SceneViewport.Reset(); if(FrameGrabber.IsValid()) { - FrameGrabber->StopCapturingFrames(); + FrameGrabber->Shutdown(); FrameGrabber.Reset(); } @@ -179,37 +179,25 @@ void FAjaMediaViewportOutputImpl::Shutdown() bool FAjaMediaViewportOutputImpl::InitAJA(UAjaMediaOutput* MediaOutput) { check(MediaOutput); - if (!MediaOutput->FillPort.IsValid()) + FString FailureReason; + if (!MediaOutput->Validate(FailureReason)) { - UE_LOG(LogAjaMediaOutput, Warning, TEXT("The FillPort of '%s' is not valid."), *MediaOutput->GetName()); + UE_LOG(LogAjaMediaOutput, Error, TEXT("Couldn't start the capture. %s"), *FailureReason); return false; } - if (MediaOutput->FillPort.DeviceIndex != MediaOutput->SyncPort.DeviceIndex || MediaOutput->FillPort.DeviceIndex != MediaOutput->KeyPort.DeviceIndex) - { - UE_LOG(LogAjaMediaOutput, Warning, TEXT("The FillPort & SyncPort & KeyPort of '%s' are not on the same device."), *MediaOutput->GetName()); - return false; - } + FrameRate = MediaOutput->MediaMode.FrameRate; AJA::AJADeviceOptions DeviceOptions(MediaOutput->FillPort.DeviceIndex); AJA::AJAInputOutputChannelOptions ChannelOptions(TEXT("ViewportOutput"), MediaOutput->FillPort.PortIndex); ChannelOptions.CallbackInterface = this; ChannelOptions.bOutput = true; - - if (!AJA::Mode2FrameDesc(MediaOutput->MediaMode.Mode, AJA::EDirectionFilter::DF_OUTPUT, ChannelOptions.FrameDesc)) - { - UE_LOG(LogAjaMediaOutput, Warning, TEXT("Mode not supported for output."), *MediaOutput->GetName()); - return false; - } - - AJA::FFrameInfo FrameInfo; - AJA::FrameDesc2Info(ChannelOptions.FrameDesc, FrameInfo); - FrameRate = FFrameRate(FrameInfo.TimeValue, FrameInfo.TimeScale); - ChannelOptions.NumberOfAudioChannel = 0; ChannelOptions.SynchronizeChannelIndex = MediaOutput->SyncPort.PortIndex; ChannelOptions.OutputKeyChannelIndex = MediaOutput->KeyPort.PortIndex; + ChannelOptions.VideoFormatIndex = MediaOutput->MediaMode.VideoFormatIndex; + ChannelOptions.PixelFormat = AJA::EPixelFormat::PF_ARGB; ChannelOptions.bUseAutoCirculating = MediaOutput->bOutputWithAutoCirculating; ChannelOptions.bOutputKey = MediaOutput->OutputType == EAjaMediaOutputType::FillAndKey; // must be RGBA to support Fill+Key ChannelOptions.bUseTimecode = bOutputTimecode; @@ -218,6 +206,19 @@ bool FAjaMediaViewportOutputImpl::InitAJA(UAjaMediaOutput* MediaOutput) ChannelOptions.bUseAudio = false; ChannelOptions.bUseVideo = true; + switch(MediaOutput->OutputReference) + { + case EAjaMediaOutputReferenceType::External: + ChannelOptions.OutputReferenceType = AJA::EAJAReferenceType::EAJA_REFERENCETYPE_EXTERNAL; + break; + case EAjaMediaOutputReferenceType::Input: + ChannelOptions.OutputReferenceType = AJA::EAJAReferenceType::EAJA_REFERENCETYPE_INPUT; + break; + default: + ChannelOptions.OutputReferenceType = AJA::EAJAReferenceType::EAJA_REFERENCETYPE_FREERUN; + break; + } + OutputChannel = new AJA::AJAOutputChannel(); if (!OutputChannel->Initialize(DeviceOptions, ChannelOptions)) { @@ -245,13 +246,20 @@ void FAjaMediaViewportOutputImpl::Tick(const FTimecode& InTimecode) EMediaState NewState = AjaThreadNewState; if (NewState != CurrentState) { - CurrentState = NewState; - check(FrameGrabber.IsValid()); if (NewState == EMediaState::Playing) { FrameGrabber->StartCapturingFrames(); } + else if (NewState == EMediaState::Error || NewState == EMediaState::Closed) + { + if (CurrentState == EMediaState::Playing) + { + FrameGrabber->StopCapturingFrames(); + } + } + + CurrentState = NewState; } if (FrameGrabber.IsValid() && OutputChannel) @@ -362,59 +370,6 @@ bool FAjaMediaViewportOutputImpl::WaitForSync() const return bResult; } -void FAjaMediaViewportOutputImpl::EncodeTimecode_Pattern(FColor* ColorBuffer, uint32 ColorBufferWidth, int32 HeightIndex, int32 Amount) const -{ - for (int32 Index = 0; Index < Amount; ++Index) - { - *(ColorBuffer + (ColorBufferWidth * HeightIndex) + Index) = (Index % 2) ? FColor::Red : FColor::Black; - } -} - -void FAjaMediaViewportOutputImpl::EncodeTimecode_Time(FColor* ColorBuffer, uint32 ColorBufferWidth, int32 HeightIndex, int32 Time) const -{ - int32 Tenth = (Time / 10); - int32 Unit = (Time % 10); - if (Tenth > 0) - { - *(ColorBuffer + (ColorBufferWidth * HeightIndex) + Tenth - 1) = FColor::White; - } - *(ColorBuffer + (ColorBufferWidth * (1 + HeightIndex)) + Unit) = FColor::White; -} - -void FAjaMediaViewportOutputImpl::EncodeTimecode(const AJA::FTimecode& Timecode, FColor* ColorBuffer, uint32 ColorBufferWidth, uint32 ColorBufferHeight) const -{ - if (bEncodeTimecodeInTexel) - { - const int32 FillWidth = 12; - const int32 FillHeight = 6*2; - - if (ColorBufferWidth > FillWidth && ColorBufferHeight > FillHeight) - { - for (int32 IndexHeight = 0; IndexHeight < FillHeight; ++IndexHeight) - { - for (int32 IndexWidth = 0; IndexWidth < FillWidth; ++IndexWidth) - { - *(ColorBuffer + (ColorBufferWidth*IndexHeight) + IndexWidth) = FColor::Black; - } - } - - EncodeTimecode_Pattern(ColorBuffer, ColorBufferWidth, 0, 2); //hh - EncodeTimecode_Pattern(ColorBuffer, ColorBufferWidth, 1, 10); - EncodeTimecode_Pattern(ColorBuffer, ColorBufferWidth, 3, 6); //mm - EncodeTimecode_Pattern(ColorBuffer, ColorBufferWidth, 4, 10); - EncodeTimecode_Pattern(ColorBuffer, ColorBufferWidth, 6, 6); //ss - EncodeTimecode_Pattern(ColorBuffer, ColorBufferWidth, 7, 10); - EncodeTimecode_Pattern(ColorBuffer, ColorBufferWidth, 9, 6); //ff - EncodeTimecode_Pattern(ColorBuffer, ColorBufferWidth, 10, 10); - - EncodeTimecode_Time(ColorBuffer, ColorBufferWidth, 0, Timecode.Hours); - EncodeTimecode_Time(ColorBuffer, ColorBufferWidth, 3, Timecode.Minutes); - EncodeTimecode_Time(ColorBuffer, ColorBufferWidth, 6, Timecode.Seconds); - EncodeTimecode_Time(ColorBuffer, ColorBufferWidth, 9, Timecode.Frames); - } - } -} - void FAjaMediaViewportOutputImpl::SendToAja(const FTimecode& FrameTimecode, FColor* ColorBuffer, uint32 ColorBufferWidth, uint32 ColorBufferHeight) { check(ColorBuffer); @@ -429,7 +384,6 @@ void FAjaMediaViewportOutputImpl::SendToAja(const FTimecode& FrameTimecode, FCol if (AjaWishResolution.X == ColorBufferWidth && AjaWishResolution.Y == ColorBufferHeight) { - EncodeTimecode(Timecode, ColorBuffer, ColorBufferWidth, ColorBufferHeight); OutputChannel->SetVideoBuffer(Timecode, reinterpret_cast(ColorBuffer), ColorBufferWidth*ColorBufferHeight * 4); } else @@ -470,7 +424,9 @@ void FAjaMediaViewportOutputImpl::SendToAja(const FTimecode& FrameTimecode, FCol UE_LOG(LogAjaMediaOutput, Log, TEXT("Aja output port %s has timecode : %02d:%02d:%02d:%02d"), *PortName, Timecode.Hours, Timecode.Minutes, Timecode.Seconds, Timecode.Frames); } - EncodeTimecode(Timecode, ColorBuffer, AjaWidth, AjaHeight); + FMediaIOCoreEncodeTime EncodeTime(EMediaTextureSampleFormat::CharBGRA, ColorBuffer, AjaWidth, AjaHeight); + EncodeTime.Render(0, 0, Timecode.Hours, Timecode.Minutes, Timecode.Seconds, Timecode.Frames); + OutputChannel->SetVideoBuffer(Timecode, reinterpret_cast(FrameData.ColorBuffer.GetData()), AjaWidth*AjaHeight* 4); } } diff --git a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutputImpl.h b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutputImpl.h index fb91d54b7739..e07250c593eb 100644 --- a/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutputImpl.h +++ b/Engine/Plugins/Media/AjaMedia/Source/AjaMediaOutput/Private/AjaMediaViewportOutputImpl.h @@ -70,9 +70,6 @@ private: void OnEndFrame_GameThread(); void OnEndFrame_RenderThread(const FTimecode& FrameTimecode, FColor* ColorBuffer, int32 Width, int32 Height); bool WaitForSync() const; - void EncodeTimecode(const AJA::FTimecode& Timecode, FColor* ColorBuffer, uint32 ColorBufferWidth, uint32 ColorBufferHeight) const; - void EncodeTimecode_Pattern(FColor* ColorBuffer, uint32 ColorBufferWidth, int32 HeightIndex, int32 Amount) const; - void EncodeTimecode_Time(FColor* ColorBuffer, uint32 ColorBufferWidth, int32 HeightIndex, int32 Time) const; void SendToAja(const FTimecode& FrameTimecode, FColor* ColorBuffer, uint32 ColorBufferWidth, uint32 ColorBufferHeight); protected: diff --git a/Engine/Plugins/Media/AudioCaptureTimecodeProvider/Source/AudioCaptureTimecodeProvider/Public/AudioCaptureTimecodeProvider.h b/Engine/Plugins/Media/AudioCaptureTimecodeProvider/Source/AudioCaptureTimecodeProvider/Public/AudioCaptureTimecodeProvider.h index baaab93129db..2ed9a58e597e 100644 --- a/Engine/Plugins/Media/AudioCaptureTimecodeProvider/Source/AudioCaptureTimecodeProvider/Public/AudioCaptureTimecodeProvider.h +++ b/Engine/Plugins/Media/AudioCaptureTimecodeProvider/Source/AudioCaptureTimecodeProvider/Public/AudioCaptureTimecodeProvider.h @@ -12,7 +12,7 @@ /** * Read the LTC from the audio capture device. */ -UCLASS(editinlinenew) +UCLASS(Blueprintable, editinlinenew) class AUDIOCAPTURETIMECODEPROVIDER_API UAudioCaptureTimecodeProvider : public UTimecodeProvider { GENERATED_UCLASS_BODY() diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/MediaFrameworkUtilities.uplugin b/Engine/Plugins/Media/MediaFrameworkUtilities/MediaFrameworkUtilities.uplugin new file mode 100644 index 000000000000..70e4562d88e7 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/MediaFrameworkUtilities.uplugin @@ -0,0 +1,29 @@ +{ + "FileVersion" : 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName" : "Media Framework Utilities", + "Description" : "Utility assets and actors to ease the use of the Media Framework.", + "Category" : "Media", + "CreatedBy" : "Epic Games, Inc.", + "CreatedByURL" : "http://epicgames.com", + "MarketplaceURL" : "", + "SupportURL" : "", + "EnabledByDefault" : false, + "CanContainContent" : true, + "IsBetaVersion" : false, + "Installed" : false, + "Modules" : + [ + { + "Name" : "MediaFrameworkUtilities", + "Type" : "Runtime", + "LoadingPhase" : "Default" + }, + { + "Name" : "MediaFrameworkUtilitiesEditor", + "Type" : "Editor", + "LoadingPhase" : "PostEngineInit" + } + ] +} diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/MediaFrameworkUtilities.Build.cs b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/MediaFrameworkUtilities.Build.cs new file mode 100644 index 000000000000..e05cad48b27f --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/MediaFrameworkUtilities.Build.cs @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class MediaFrameworkUtilities : ModuleRules + { + public MediaFrameworkUtilities(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "Media", + "MediaAssets", + "MediaUtils", + }); + } + } +} diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaBundle.cpp b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaBundle.cpp new file mode 100644 index 000000000000..9b479aa1ebc7 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaBundle.cpp @@ -0,0 +1,62 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MediaBundle.h" + +#include "IMediaControls.h" +#include "IMediaPlayer.h" +#include "MediaPlayer.h" +#include "MediaPlayerFacade.h" +#include "MediaSource.h" +#include "MediaTexture.h" +#include "Materials/MaterialInterface.h" + + +/* UMediaBundle + *****************************************************************************/ + +bool UMediaBundle::OpenMediaSource() +{ + bool bResult = false; + if (MediaSource) + { + bResult = true; + + // Only play once + const EMediaState MediaState = MediaPlayer->GetPlayerFacade()->GetPlayer().IsValid() ? MediaPlayer->GetPlayerFacade()->GetPlayer()->GetControls().GetState() : EMediaState::Closed; + if (MediaState == EMediaState::Closed || MediaState == EMediaState::Error) + { + bResult = MediaPlayer->OpenSource(MediaSource); + } + + if (bResult) + { + ++ReferenceCount; + } + } + return bResult; +} + +void UMediaBundle::CloseMediaSource() +{ + --ReferenceCount; + if (ReferenceCount == 0) + { + MediaPlayer->Close(); + } +} + +#if WITH_EDITOR +void UMediaBundle::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.GetPropertyName() == GET_MEMBER_NAME_CHECKED(UMediaBundle, MediaSource)) + { + MediaPlayer->Close(); + if (MediaSource && ReferenceCount > 0) + { + MediaPlayer->OpenSource(MediaSource); + } + } +} +#endif diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaBundleActorBase.cpp b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaBundleActorBase.cpp new file mode 100644 index 000000000000..8ac262346140 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaBundleActorBase.cpp @@ -0,0 +1,189 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MediaBundleActorBase.h" + +#include "Components/PrimitiveComponent.h" +#include "MediaBundle.h" +#include "MediaSoundComponent.h" + +void AMediaBundleActorBase::SetComponent(UPrimitiveComponent* InPrimitive, UMediaSoundComponent* InMediaSound) +{ + if (InPrimitive != PrimitiveCmp) + { + if (MediaBundle && PrimitiveCmp && PrimitiveCmp->GetMaterial(PrimitiveMaterialIndex) == MediaBundle->GetMaterial()) + { + PrimitiveCmp->SetMaterial(PrimitiveMaterialIndex, nullptr); + } + PrimitiveCmp = InPrimitive; + if (MediaBundle && PrimitiveCmp) + { + PrimitiveCmp->SetMaterial(PrimitiveMaterialIndex, MediaBundle->GetMaterial()); + } + } + + if (InMediaSound != MediaSoundCmp) + { + if (MediaBundle && MediaSoundCmp && MediaSoundCmp->GetMediaPlayer() == MediaBundle->GetMediaPlayer()) + { + MediaSoundCmp->SetMediaPlayer(nullptr); + } + MediaSoundCmp = InMediaSound; + if (MediaBundle && MediaSoundCmp) + { + MediaSoundCmp->SetMediaPlayer(MediaBundle->GetMediaPlayer()); + } + } +} + +bool AMediaBundleActorBase::RequestOpenMediaSource() +{ + if (HasAnyFlags(RF_ClassDefaultObject)) + { + return false; + } + if (bPlayingMedia) + { + return true; + } + + bPlayingMedia = MediaBundle ? MediaBundle->OpenMediaSource() : false; + return bPlayingMedia; +} + +void AMediaBundleActorBase::RequestCloseMediaSource() +{ + if (MediaBundle && bPlayingMedia) + { + MediaBundle->CloseMediaSource(); + bPlayingMedia = false; + } +} + +void AMediaBundleActorBase::BeginPlay() +{ + Super::BeginPlay(); + + if (bAutoPlay) + { + RequestOpenMediaSource(); + } +} + +void AMediaBundleActorBase::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + RequestCloseMediaSource(); + + Super::EndPlay(EndPlayReason); +} + +void AMediaBundleActorBase::PostActorCreated() +{ + Super::PostActorCreated(); + + if (!HasAnyFlags(RF_Transient)) + { + if (bAutoPlay && bPlayWhileEditing) + { + RequestOpenMediaSource(); + } + } +} + +void AMediaBundleActorBase::PostLoadSubobjects(FObjectInstancingGraph* OuterInstanceGraph) +{ + Super::PostLoadSubobjects(OuterInstanceGraph); + + if (MediaBundle && PrimitiveCmp) + { + PrimitiveCmp->SetMaterial(PrimitiveMaterialIndex, MediaBundle->GetMaterial()); + } + + if (MediaBundle && MediaSoundCmp) + { + MediaSoundCmp->SetMediaPlayer(MediaBundle->GetMediaPlayer()); + } + + if (bAutoPlay && bPlayWhileEditing) + { + RequestOpenMediaSource(); + } +} + +void AMediaBundleActorBase::Destroyed() +{ + RequestCloseMediaSource(); + + Super::Destroyed(); +} + +#if WITH_EDITOR +void AMediaBundleActorBase::PreEditChange(UProperty* PropertyAboutToChange) +{ + Super::PreEditChange(PropertyAboutToChange); + + if (PropertyAboutToChange) + { + bool bResetComponent = false; + if (PropertyAboutToChange->GetFName() == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, MediaBundle) + || PropertyAboutToChange->GetFName() == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, bAutoPlay) + || PropertyAboutToChange->GetFName() == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, bPlayWhileEditing)) + { + bResetComponent = PropertyAboutToChange->GetFName() == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, MediaBundle); + RequestCloseMediaSource(); + } + else if (PropertyAboutToChange->GetFName() == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, PrimitiveCmp) + || PropertyAboutToChange->GetFName() == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, MediaSoundCmp)) + { + bResetComponent = true; + } + + + if (bResetComponent && MediaBundle) + { + if (PrimitiveCmp && PrimitiveCmp->GetMaterial(PrimitiveMaterialIndex) == MediaBundle->GetMaterial()) + { + PrimitiveCmp->SetMaterial(PrimitiveMaterialIndex, nullptr); + } + else if (MediaSoundCmp && MediaSoundCmp->GetMediaPlayer() == MediaBundle->GetMediaPlayer()) + { + MediaSoundCmp->SetMediaPlayer(nullptr); + } + } + } +} + +void AMediaBundleActorBase::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + bool bSetComponent = false; + const FName PropertyName = PropertyChangedEvent.GetPropertyName(); + if (PropertyName == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, MediaBundle) + || PropertyName == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, bAutoPlay) + || PropertyName == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, bPlayWhileEditing)) + { + if ((HasActorBegunPlay() && bAutoPlay) || (bPlayWhileEditing && bAutoPlay)) + { + bPlayingMedia = RequestOpenMediaSource(); + } + bSetComponent = PropertyName == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, MediaBundle); + } + else if (PropertyName == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, PrimitiveCmp) + || PropertyName == GET_MEMBER_NAME_CHECKED(AMediaBundleActorBase, MediaSoundCmp)) + { + bSetComponent = true; + } + + if (bSetComponent && MediaBundle) + { + if (PrimitiveCmp) + { + PrimitiveCmp->SetMaterial(PrimitiveMaterialIndex, MediaBundle->GetMaterial()); + } + if (MediaSoundCmp) + { + MediaSoundCmp->SetMediaPlayer(MediaBundle->GetMediaPlayer()); + } + } +} +#endif //WITH_EDITOR diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaFrameworkUtilitiesModule.cpp b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaFrameworkUtilitiesModule.cpp new file mode 100644 index 000000000000..d080898df528 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Private/MediaFrameworkUtilitiesModule.cpp @@ -0,0 +1,15 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +/** + * Implements the MediaFrameworkUtilitiesModule module. + */ +class FMediaFrameworkUtilitiesModule : public IModuleInterface +{ +}; + +IMPLEMENT_MODULE(FMediaFrameworkUtilitiesModule, MediaFrameworkUtilitiesModule); + diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Public/MediaBundle.h b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Public/MediaBundle.h new file mode 100644 index 000000000000..a7b24e7a9f20 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Public/MediaBundle.h @@ -0,0 +1,93 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "UObject/Object.h" +#include "UObject/ScriptMacros.h" + +#if WITH_EDITORONLY_DATA +#include "MediaBundleActorBase.h" +#endif + +#include "MediaBundle.generated.h" + +class UMediaTexture; +class UMediaPlayer; +class UMediaSource; +class UMaterialInterface; + +/** + * A bundle of the Media Asset necessary to play a video & audio + */ +UCLASS(BlueprintType, hidecategories=(Object)) +class MEDIAFRAMEWORKUTILITIES_API UMediaBundle : public UObject +{ + GENERATED_BODY() + +public: + /** + * Get the material interface. + */ + UFUNCTION(BlueprintCallable, Category = "Media|MediaBundle") + UMaterialInterface* GetMaterial() { return Material; } + + /** + * Get the media player. + */ + UFUNCTION(BlueprintCallable, Category="Media|MediaBundle") + UMediaPlayer* GetMediaPlayer() { return MediaPlayer; } + + /** + * Get the media texture. + */ + UFUNCTION(BlueprintCallable, Category="Media|MediaBundle") + UMediaTexture* GetMediaTexture() { return MediaTexture; } + + /** + * Get the media source. + */ + UFUNCTION(BlueprintCallable, Category="Media|MediaBundle") + UMediaSource* GetMediaSource() { return MediaSource; } + +public: + UPROPERTY(EditAnywhere, Instanced, NoClear, Category="MediaBundle") + UMediaSource* MediaSource; + +#if WITH_EDITORONLY_DATA + /* Class to spawn for that asset. */ + UPROPERTY(AdvancedDisplay, EditAnywhere, NoClear, Category="MediaBundle") + TSubclassOf MediaBundleActorClass; +#endif + +protected: + UPROPERTY(Instanced) + UMediaPlayer* MediaPlayer; + + UPROPERTY(Instanced) + UMediaTexture* MediaTexture; + + UPROPERTY(Instanced) + UMaterialInterface* Material; + +private: + UPROPERTY(AdvancedDisplay, DuplicateTransient, Transient, VisibleDefaultsOnly, Category="MediaBundle", meta=(DisplayName="Debug: Reference Count")) + int32 ReferenceCount; + +public: + /** + * Play the media source. Only open the source if the reference count is 0. (ie. no one else opened it) + */ + bool OpenMediaSource(); + + /** + * Close the media source. Only close the source if the reference count is 1. (ie. last one to close it) + */ + void CloseMediaSource(); + + //~ UObject interface +#if WITH_EDITOR + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + friend class UMediaBundleFactoryNew; +#endif +}; diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Public/MediaBundleActorBase.h b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Public/MediaBundleActorBase.h new file mode 100644 index 000000000000..85baf7cd4b29 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilities/Public/MediaBundleActorBase.h @@ -0,0 +1,86 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "GameFramework/Actor.h" + +#include "MediaBundleActorBase.generated.h" + +class UMediaBundle; +class UMediaSoundComponent; +class UPrimitiveComponent; + +/** + * A base actor that + */ +UCLASS(abstract, Blueprintable) +class MEDIAFRAMEWORKUTILITIES_API AMediaBundleActorBase : public AActor +{ + GENERATED_BODY() + +public: + /** + * Get the Media Bundle. + */ + UFUNCTION(BlueprintCallable, Category = "Media|MediaBundle") + UMediaBundle* GetMediaBundle() { return MediaBundle; } + + /** + * Play the Media Source. + */ + UFUNCTION(BlueprintCallable, Category="Media|MediaBundle") + bool RequestOpenMediaSource(); + + /** + * Close the Media Source. + */ + UFUNCTION(BlueprintCallable, Category="Media|MediaBundle") + void RequestCloseMediaSource(); + + + /** + * Whether this actor requested the media to play. + */ + bool IsPlayRequested() const { return bPlayingMedia; } + +protected: + /** + * Assign the primitive to render on. Will change the material for the Media material. + */ + UFUNCTION(BlueprintCallable, Category="Media|MediaBundle") + void SetComponent(UPrimitiveComponent* InPrimitive, UMediaSoundComponent* InMediaSound); + +protected: + UPROPERTY(EditAnywhere, NoClear, Category="MediaBundle") + UMediaBundle* MediaBundle; + + UPROPERTY(EditDefaultsOnly, Category="MediaBundle") + bool bAutoPlay; + + UPROPERTY(EditDefaultsOnly, Category="MediaBundle", meta=(EditCondition="bAutoPlay")) + bool bPlayWhileEditing; + + UPROPERTY(EditDefaultsOnly, Category="MediaBundle") + UPrimitiveComponent* PrimitiveCmp; + + UPROPERTY(EditDefaultsOnly, NoClear, Category = "MediaBundle") + UMediaSoundComponent* MediaSoundCmp; + + UPROPERTY(AdvancedDisplay, EditAnywhere, Category="MediaBundle") + int32 PrimitiveMaterialIndex; + + bool bPlayingMedia; + +public: + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void PostLoadSubobjects(FObjectInstancingGraph* OuterInstanceGraph); + virtual void PostActorCreated() override; + virtual void Destroyed() override; + +#if WITH_EDITOR + virtual void PreEditChange(UProperty* PropertyAboutToChange) override; + virtual void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override; + friend class UActorFactoryMediaBundle; +#endif +}; diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/MediaFrameworkUtilitiesEditor.Build.cs b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/MediaFrameworkUtilitiesEditor.Build.cs new file mode 100644 index 000000000000..c9ec01abeaf6 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/MediaFrameworkUtilitiesEditor.Build.cs @@ -0,0 +1,29 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class MediaFrameworkUtilitiesEditor : ModuleRules + { + public MediaFrameworkUtilitiesEditor(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "AssetRegistry", + "Core", + "CoreUObject", + "EditorStyle", + "Engine", + "InputCore", + "MediaAssets", + "MediaFrameworkUtilities", + "MediaUtils", + "PlacementMode", + "PropertyEditor", + "Slate", + "SlateCore", + "UnrealEd", + }); + } + } +} diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleActorDetails.cpp b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleActorDetails.cpp new file mode 100644 index 000000000000..257ba2e0d61c --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleActorDetails.cpp @@ -0,0 +1,107 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MediaBundleActorDetails.h" + +#include "DetailCategoryBuilder.h" +#include "DetailLayoutBuilder.h" +#include "DetailWidgetRow.h" +#include "Modules/ModuleManager.h" +#include "Framework/SlateDelegates.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/SNullWidget.h" + +#include "MediaBundleActorBase.h" + +#define LOCTEXT_NAMESPACE "MediaBundleActorDetails" + +TSharedRef FMediaBundleActorDetails::MakeInstance() +{ + return MakeShareable( new FMediaBundleActorDetails); +} + +void FMediaBundleActorDetails::CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) +{ + IDetailCategoryBuilder& MediaBundleCategory = DetailBuilder.EditCategory( "MediaBundle" ); + + const bool bForAdvanced = false; + const FText PlayTextString = LOCTEXT("PlayMedia", "Request Play Media"); + const FText CloseTextString = LOCTEXT("CloseMedia", "Request Close Media"); + + TArray> Objects; + DetailBuilder.GetObjectsBeingCustomized(Objects); + + TSharedPtr>> ActorsListPtr = MakeShared>>(); + ActorsListPtr->Reserve(Objects.Num()); + for(TWeakObjectPtr& Obj : Objects) + { + TWeakObjectPtr ActorPtr = Cast(Obj.Get()); + if (ActorPtr.IsValid()) + { + ActorsListPtr->Add(ActorPtr); + } + } + + + MediaBundleCategory.AddCustomRow(PlayTextString, bForAdvanced ) + .NameContent() + [ + SNullWidget::NullWidget + ] + .ValueContent() + .VAlign(VAlign_Center) + .MaxDesiredWidth(250) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .AutoWidth() + [ + SNew(SButton) + .VAlign(VAlign_Center) + .Text(PlayTextString) + .IsEnabled_Lambda([ActorsListPtr]() + { + return ActorsListPtr->ContainsByPredicate([&](const TWeakObjectPtr& Other) { return !Other->IsPlayRequested(); }); + }) + .OnClicked_Lambda([ActorsListPtr]() -> FReply + { + for(auto& ActorPtr : *ActorsListPtr) + { + if (AMediaBundleActorBase* Actor = ActorPtr.Get()) + { + Actor->RequestOpenMediaSource(); + } + } + return FReply::Handled(); + }) + ] + + + SHorizontalBox::Slot() + .AutoWidth() + .VAlign(VAlign_Center) + .Padding(4, 0, 0, 0) + [ + SNew(SButton) + .VAlign(VAlign_Center) + .Text(CloseTextString) + .IsEnabled_Lambda([ActorsListPtr]() + { + return ActorsListPtr->ContainsByPredicate([&](const TWeakObjectPtr& Other) { return Other->IsPlayRequested(); }); + }) + .OnClicked(FOnClicked::CreateLambda([ActorsListPtr]() + { + for (auto& ActorPtr : *ActorsListPtr) + { + if (AMediaBundleActorBase* Actor = ActorPtr.Get()) + { + Actor->RequestCloseMediaSource(); + } + } + return FReply::Handled(); + })) + ] + ]; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleActorDetails.h b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleActorDetails.h new file mode 100644 index 000000000000..fc7171f951a5 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleActorDetails.h @@ -0,0 +1,18 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IDetailCustomization.h" + +class IDetailLayoutBuilder; + +class FMediaBundleActorDetails : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef MakeInstance(); + + /** IDetailCustomization interface */ + virtual void CustomizeDetails( IDetailLayoutBuilder& DetailBuilder ) override; +}; diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleFactoryNew.cpp b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleFactoryNew.cpp new file mode 100644 index 000000000000..b28191c7b981 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleFactoryNew.cpp @@ -0,0 +1,134 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MediaBundleFactoryNew.h" + +#include "AssetTypeCategories.h" +#include "Engine/Blueprint.h" +#include "Factories/MaterialInstanceConstantFactoryNew.h" +#include "MediaBundle.h" +#include "MediaPlayer.h" +#include "MediaTexture.h" +#include "Materials/MaterialInstanceConstant.h" +#include "UObject/ConstructorHelpers.h" + +#define LOCTEXT_NAMESPACE "MediaBundleFactoryNew" + + +/* UMediaBundleFactoryNew structors + *****************************************************************************/ + +UMediaBundleFactoryNew::UMediaBundleFactoryNew( const FObjectInitializer& ObjectInitializer ) + : Super(ObjectInitializer) +{ + static ConstructorHelpers::FObjectFinder DefaultMaterialFinder(TEXT("/MediaFrameworkUtilities/M_DefaultMedia")); + static ConstructorHelpers::FClassFinder DefaultActorClassFinder(TEXT("/MediaFrameworkUtilities/BP_MediaBundle_Plane_16-9")); + + DefaultMaterial = DefaultMaterialFinder.Object; + DefaultActorClass = DefaultActorClassFinder.Class; + + SupportedClass = UMediaBundle::StaticClass(); + bCreateNew = true; + bEditAfterNew = true; +} + + +/* UMediaBundleFactoryNew UFactory interface + *****************************************************************************/ + +UObject* UMediaBundleFactoryNew::FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) +{ + UMediaBundle* NewMediaBundle = NewObject(InParent, InClass, InName, Flags); + + NewMediaBundle->MediaPlayer = NewObject(NewMediaBundle, UMediaPlayer::StaticClass(), *(TEXT("MediaP_") + InName.ToString()), Flags); + NewMediaBundle->MediaPlayer->AffectedByPIEHandling = false; + + NewMediaBundle->MediaTexture = NewObject(NewMediaBundle, UMediaTexture::StaticClass(), *(+TEXT("T_") + InName.ToString() + TEXT("_BC")), Flags); + NewMediaBundle->MediaTexture->SetDefaultMediaPlayer(NewMediaBundle->MediaPlayer); + + UMaterialInstanceConstantFactoryNew* Factory = NewObject(); + Factory->InitialParent = DefaultMaterial; + UMaterialInstanceConstant* NewMaterial = Cast(Factory->FactoryCreateNew(UMaterialInstanceConstant::StaticClass(), NewMediaBundle, *(TEXT("MI_") + InName.ToString()), Flags, Context, Warn)); + NewMaterial->SetTextureParameterValueEditorOnly(FMaterialParameterInfo("MediaTexture"), NewMediaBundle->MediaTexture); + NewMaterial->PostEditChange(); + + NewMediaBundle->MediaBundleActorClass = DefaultActorClass; + + NewMediaBundle->Material = NewMaterial; + + return NewMediaBundle; +} + +uint32 UMediaBundleFactoryNew::GetMenuCategories() const +{ + return EAssetTypeCategories::Media; +} + +bool UMediaBundleFactoryNew::ShouldShowInNewMenu() const +{ + return true; +} + +/* UActorFactoryMediaBundle structors +*****************************************************************************/ + +UActorFactoryMediaBundle::UActorFactoryMediaBundle(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + DisplayName = LOCTEXT("FactoryMediaBundleDisplayName", "Media Bundle Actor"); + NewActorClass = AMediaBundleActorBase::StaticClass(); + bUseSurfaceOrientation = false; +} + + +/* UActorFactoryMediaBundle UFactory interface +*****************************************************************************/ +bool UActorFactoryMediaBundle::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg) +{ + if (!AssetData.IsValid() || !AssetData.GetClass()->IsChildOf(UMediaBundle::StaticClass())) + { + OutErrorMsg = LOCTEXT("NoMediaBundle", "A valid Media Bundle must be specified."); + return false; + } + + return true; +} + +void UActorFactoryMediaBundle::PostSpawnActor(UObject* Asset, AActor* NewActor) +{ + Super::PostSpawnActor(Asset, NewActor); + + UMediaBundle* MediaBundle = CastChecked(Asset); + AMediaBundleActorBase* MediaBundleActor = CastChecked(NewActor); + + UProperty* MediaBundleProperty = FindFieldChecked(AMediaBundleActorBase::StaticClass(), "MediaBundle"); + FEditPropertyChain PropertyChain; + PropertyChain.AddHead(MediaBundleProperty); + static_cast(NewActor)->PreEditChange(PropertyChain); + + MediaBundleActor->MediaBundle = MediaBundle; + + FPropertyChangedEvent PropertyEvent(MediaBundleProperty); + NewActor->PostEditChangeProperty(PropertyEvent); +} + +UObject* UActorFactoryMediaBundle::GetAssetFromActorInstance(AActor* ActorInstance) +{ + check(ActorInstance->IsA(NewActorClass)); + AMediaBundleActorBase* MBA = CastChecked(ActorInstance); + + return MBA->GetMediaBundle(); +} + +AActor* UActorFactoryMediaBundle::GetDefaultActor(const FAssetData& AssetData) +{ + if (UMediaBundle* Bundle = Cast(AssetData.GetAsset())) + { + if (UClass* ActorClass = Bundle->MediaBundleActorClass.Get()) + { + return ActorClass->GetDefaultObject(); + } + } + return Super::GetDefaultActor(AssetData); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleFactoryNew.h b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleFactoryNew.h new file mode 100644 index 000000000000..b2a18feb4c77 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaBundleFactoryNew.h @@ -0,0 +1,55 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" + +#include "ActorFactories/ActorFactory.h" +#include "Factories/Factory.h" + +#include "MediaBundleActorBase.h" + +#include "MediaBundleFactoryNew.generated.h" + +class UMaterial; + +/** + * Implements a factory for UMediaPlayer objects. + */ +UCLASS(hideCategories =Object) +class UMediaBundleFactoryNew : public UFactory +{ + GENERATED_UCLASS_BODY() + +public: + //~ Begin UFactory Interface + virtual UObject* FactoryCreateNew(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, UObject* Context, FFeedbackContext* Warn) override; + virtual uint32 GetMenuCategories() const override; + virtual bool ShouldShowInNewMenu() const override; + //~ End UFactory Interface + +private: + UPROPERTY(transient) + UMaterial* DefaultMaterial; + + UPROPERTY(transient) + TSubclassOf DefaultActorClass; +}; + +UCLASS(MinimalAPI, config=Editor, collapseCategories, hideCategories=Object) +class UActorFactoryMediaBundle : public UActorFactory +{ + GENERATED_UCLASS_BODY() + +public: + //~ 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; + virtual AActor* GetDefaultActor(const FAssetData& AssetData) override; + //~ End UActorFactory Interface +}; + + + diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesEditorModule.cpp b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesEditorModule.cpp new file mode 100644 index 000000000000..38def38b2e57 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesEditorModule.cpp @@ -0,0 +1,57 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +#include "Editor.h" +#include "Editor/UnrealEdEngine.h" +#include "PropertyEditorDelegates.h" +#include "PropertyEditorModule.h" +#include "UObject/UObjectBase.h" + +#include "MediaBundleActorDetails.h" +#include "MediaBundleFactoryNew.h" +#include "MediaFrameworkUtilitiesPlacement.h" + +#define LOCTEXT_NAMESPACE "MediaFrameworkEditor" + +/** + * Implements the MediaPlayerEditor module. + */ +class FMediaFrameworkUtilitiesEditorModule : public IModuleInterface +{ +public: + + //~ IModuleInterface interface + + virtual void StartupModule() override + { + if (GEditor) + { + GEditor->ActorFactories.Add(NewObject()); + } + FMediaFrameworkUtilitiesPlacement::RegisterPlacement(); + + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.RegisterCustomClassLayout("MediaBundleActorBase", FOnGetDetailCustomizationInstance::CreateStatic(&FMediaBundleActorDetails::MakeInstance)); + } + + virtual void ShutdownModule() override + { + if (!GIsRequestingExit && GEditor && UObjectInitialized()) + { + GEditor->ActorFactories.RemoveAll([](const UActorFactory* ActorFactory) { return ActorFactory->IsA(); }); + FMediaFrameworkUtilitiesPlacement::UnregisterPlacement(); + + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked("PropertyEditor"); + PropertyModule.UnregisterCustomClassLayout("MediaBundleActorBase"); + } + } +}; + + +IMPLEMENT_MODULE(FMediaFrameworkUtilitiesEditorModule, MediaFrameworkUtilitiesEditorModule); + + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesPlacement.cpp b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesPlacement.cpp new file mode 100644 index 000000000000..58c5ffa73853 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesPlacement.cpp @@ -0,0 +1,224 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "MediaFrameworkUtilitiesPlacement.h" + +#include "Application/SlateApplicationBase.h" +#include "AssetThumbnail.h" +#include "EditorStyleSet.h" +#include "SlateOptMacros.h" +#include "Styling/SlateIconFinder.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/Layout/SScrollBorder.h" +#include "Widgets/Views/STableViewBase.h" +#include "Widgets/Views/STableRow.h" +#include "Widgets/Views/SListView.h" + +#include "AssetData.h" +#include "AssetRegistryModule.h" +#include "DragAndDrop/AssetDragDropOp.h" +#include "IPlacementModeModule.h" +#include "IAssetRegistry.h" +#include "LevelEditor.h" + +#include "MediaBundle.h" +#include "MediaSource.h" + + +#define LOCTEXT_NAMESPACE "MediaFrameworkEditor" + +/** The asset of the asset view */ +struct FMediaPlacementListItem +{ + FMediaPlacementListItem() = default; + + bool IsValid() const + { + return MediaBundle.IsUAsset(); + } + + FText DisplayName; + FAssetData MediaBundle; +}; + +/** The list view mode of the asset view */ +class SMediaPlacementListView : public SListView> +{ +public: + virtual bool SupportsKeyboardFocus() const override + { + return false; + } + virtual FReply OnKeyDown(const FGeometry& InGeometry, const FKeyEvent& InKeyEvent) override + { + return FReply::Unhandled(); + } +}; + +/** The Placement compound widget */ +class SMediaPlacementPalette : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SMediaPlacementPalette) {} + SLATE_END_ARGS(); + + BEGIN_SLATE_FUNCTION_BUILD_OPTIMIZATION + void Construct(const FArguments& InArgs) + { + BuildList(); + + TSharedRef ListViewWidget = + SNew(SMediaPlacementListView) + .SelectionMode(ESelectionMode::Single) + .ListItemsSource(&PlacementList) + .OnGenerateRow(this, &SMediaPlacementPalette::MakeListViewWidget) + .OnSelectionChanged(this, &SMediaPlacementPalette::OnSelectionChanged) + .ItemHeight(35); + + ChildSlot + [ + SNew(SScrollBorder, ListViewWidget) + [ + ListViewWidget + ] + ]; + } + END_SLATE_FUNCTION_BUILD_OPTIMIZATION + + TSharedRef MakeListViewWidget(TSharedPtr MediaPlacement, const TSharedRef& OwnerTable) + { + if (!MediaPlacement.IsValid() || !MediaPlacement->IsValid()) + { + return SNew(STableRow>, OwnerTable); + } + + TSharedRef< STableRow> > TableRowWidget = + SNew(STableRow>, OwnerTable) + .Style(FEditorStyle::Get(), "ContentBrowser.AssetListView.TableRow") + .OnDragDetected(this, &SMediaPlacementPalette::OnDraggingListViewWidget); + + // Get the MediaSource thumbnail or the MediaBundle is not loaded + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked("LevelEditor"); + TSharedPtr ThumbnailPool = LevelEditorModule.GetFirstLevelEditor()->GetThumbnailPool(); + + FAssetData ThumbnailAssetData = MediaPlacement->MediaBundle; + if (MediaPlacement->MediaBundle.IsAssetLoaded()) + { + UMediaBundle* MediaBundle = Cast(MediaPlacement->MediaBundle.GetAsset()); + if (MediaBundle) + { + ThumbnailAssetData = FAssetData(MediaBundle->MediaSource); + } + } + TSharedPtr< FAssetThumbnail > Thumbnail = MakeShareable(new FAssetThumbnail(ThumbnailAssetData, 32, 32, ThumbnailPool)); + + // Create the TableRow content + TSharedRef Content = + SNew(SBorder) + .BorderImage(FCoreStyle::Get().GetBrush("NoBorder")) + .Padding(0) + .Cursor(EMouseCursor::GrabHand) + [ + SNew(SHorizontalBox) + // Icon + + SHorizontalBox::Slot() + .Padding(0) + .AutoWidth() + [ + SNew(SBorder) + .Padding(4.0f) + .BorderImage(FEditorStyle::GetBrush("ContentBrowser.ThumbnailShadow")) + [ + SNew(SBox) + .WidthOverride(35.0f) + .HeightOverride(35.0f) + [ + SNew(SBorder) + .BorderImage(FEditorStyle::GetBrush("ContentBrowser.ThumbnailShadow")) + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + Thumbnail->MakeThumbnailWidget() + ] + ] + ] + ] + + + SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .Padding(2, 0, 4, 0) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + .Padding(0, 0, 0, 1) + .AutoHeight() + [ + SNew(STextBlock) + .TextStyle(FEditorStyle::Get(), "PlacementBrowser.Asset.Name") + .Text(MediaPlacement->DisplayName) + ] + ] + ]; + + TableRowWidget->SetContent(Content); + + return TableRowWidget; + + } + + void OnSelectionChanged(TSharedPtr MediaPlacement, ESelectInfo::Type SelectionType) + { + SelectedMediaPlacement = MediaPlacement; + } + + FReply OnDraggingListViewWidget(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) + { + if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + { + if (SelectedMediaPlacement.IsValid()) + { + // We have an active brush builder, start a drag-drop + TArray InAssetData; + InAssetData.Add(SelectedMediaPlacement->MediaBundle); + return FReply::Handled().BeginDragDrop(FAssetDragDropOp::New(InAssetData)); + } + } + + return FReply::Unhandled(); + } + + void BuildList() + { + FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked(TEXT("AssetRegistry")); + + TArray AssetDatas; + AssetRegistryModule.Get().GetAssetsByClass("MediaBundle", AssetDatas, true); + PlacementList.Reset(AssetDatas.Num()); + + for (const FAssetData& AssetData : AssetDatas) + { + TSharedPtr NewItem = MakeShared(); + NewItem->DisplayName = FText::FromName(AssetData.AssetName); + NewItem->MediaBundle = AssetData; + PlacementList.Add(NewItem); + } + } + + TSharedPtr SelectedMediaPlacement; + TArray> PlacementList; +}; + +void FMediaFrameworkUtilitiesPlacement::RegisterPlacement() +{ + IPlacementModeModule& PlacementModeModule = IPlacementModeModule::Get(); + FName CategoryName = "Media"; + FPlacementCategoryInfo CategoryInfo(LOCTEXT("PlacementMode_Media", "Media"), CategoryName, TEXT("PMMedia"), 35); + CategoryInfo.CustomGenerator = []() -> TSharedRef { return SNew(SMediaPlacementPalette); }; + PlacementModeModule.RegisterPlacementCategory(CategoryInfo); +} + +void FMediaFrameworkUtilitiesPlacement::UnregisterPlacement() +{ + IPlacementModeModule& PlacementModeModule = IPlacementModeModule::Get(); + PlacementModeModule.UnregisterPlacementCategory(TEXT("PMMedia")); +} diff --git a/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesPlacement.h b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesPlacement.h new file mode 100644 index 000000000000..45ed34163cf7 --- /dev/null +++ b/Engine/Plugins/Media/MediaFrameworkUtilities/Source/MediaFrameworkUtilitiesEditor/Private/MediaFrameworkUtilitiesPlacement.h @@ -0,0 +1,12 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +class FMediaFrameworkUtilitiesPlacement +{ +public: + static void RegisterPlacement(); + static void UnregisterPlacement(); +}; diff --git a/Engine/Plugins/Media/MediaPlayerEditor/Source/MediaPlayerEditor/Private/MediaPlayerEditorModule.cpp b/Engine/Plugins/Media/MediaPlayerEditor/Source/MediaPlayerEditor/Private/MediaPlayerEditorModule.cpp index 2967d89c39ec..0b473440db81 100644 --- a/Engine/Plugins/Media/MediaPlayerEditor/Source/MediaPlayerEditor/Private/MediaPlayerEditorModule.cpp +++ b/Engine/Plugins/Media/MediaPlayerEditor/Source/MediaPlayerEditor/Private/MediaPlayerEditorModule.cpp @@ -269,7 +269,11 @@ private: { for (TObjectIterator It; It; ++It) { - (*It)->Close(); + UMediaPlayer* Player = *It; + if (Player->AffectedByPIEHandling) + { + Player->Close(); + } } } @@ -277,7 +281,11 @@ private: { for (TObjectIterator It; It; ++It) { - (*It)->Close(); + UMediaPlayer* Player = *It; + if (Player->AffectedByPIEHandling) + { + (*It)->Close(); + } } } @@ -285,7 +293,11 @@ private: { for (TObjectIterator It; It; ++It) { - (*It)->PausePIE(); + UMediaPlayer* Player = *It; + if (Player->AffectedByPIEHandling) + { + (*It)->PausePIE(); + } } } @@ -293,7 +305,11 @@ private: { for (TObjectIterator It; It; ++It) { - (*It)->ResumePIE(); + UMediaPlayer* Player = *It; + if (Player->AffectedByPIEHandling) + { + (*It)->ResumePIE(); + } } } diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Private/MediaPlayerInputSource.cpp b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Private/MediaPlayerInputSource.cpp index 71905c42d754..a7ed0f6ff483 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Private/MediaPlayerInputSource.cpp +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Private/MediaPlayerInputSource.cpp @@ -37,7 +37,7 @@ FFrameTime UMediaPlayerInputSource::GetNextSampleTime() const if (MediaTexture->GetAvailableSampleCount() > 0) { const FTimespan TextureTime = MediaTexture->GetNextSampleTime(); - NextSampleTime = FFrameTime::FromDecimal(TextureTime.GetTotalSeconds() * PlayerFrameRate.AsDecimal()).RoundToFrame(); + NextSampleTime = FFrameTime::FromDecimal(TextureTime.GetTotalSeconds() * GetFrameRate().AsDecimal()).RoundToFrame(); } else if (Player->GetCache().GetSampleCount(EMediaCacheState::Loaded) > 0) { @@ -54,13 +54,9 @@ FFrameTime UMediaPlayerInputSource::GetNextSampleTime() const { MinBound = TRangeBound::MinLower(MinBound, Range.GetLowerBound()); } - const FTimespan MinSampleTime = MinBound.GetValue(); - - //todo : Next time sequencer is integrated to main, get changes to TRangeSet to be able to use this code. (CL 3967195) - //const FTimespan& MinSampleTime = SampleTimes.GetMinBoundValue(); - NextSampleTime = FFrameTime::FromDecimal(MinSampleTime.GetTotalSeconds() * PlayerFrameRate.AsDecimal()).RoundToFrame(); + NextSampleTime = FFrameTime::FromDecimal(MinSampleTime.GetTotalSeconds() * GetFrameRate().AsDecimal()).RoundToFrame(); } } } @@ -84,7 +80,19 @@ int32 UMediaPlayerInputSource::GetAvailableSampleCount() const FFrameRate UMediaPlayerInputSource::GetFrameRate() const { - return PlayerFrameRate; + const auto& Player = MediaPlayer->GetPlayerFacade()->GetPlayer(); + if (Player.IsValid() && IsReady()) + { + //Save the FrameRate of the current track of the media player for future use + const int32 SelectedTrack = MediaPlayer->GetSelectedTrack(EMediaPlayerTrack::Video); + const int32 SelectedFormat = MediaPlayer->GetTrackFormat(EMediaPlayerTrack::Video, SelectedTrack); + const float FrameRate = MediaPlayer->GetVideoTrackFrameRate(SelectedTrack, SelectedFormat); + + //Convert using 1001 for DropFrame FrameRate detection + const uint32 Precision = 1001; + return FFrameRate(FMath::RoundToInt(FrameRate * Precision), Precision); + } + return FFrameRate(); } bool UMediaPlayerInputSource::IsReady() const @@ -111,18 +119,7 @@ bool UMediaPlayerInputSource::Open() else { const auto& Player = MediaPlayer->GetPlayerFacade()->GetPlayer(); - if (Player.IsValid()) - { - //Save the FrameRate of the current track of the media player for future use - const int32 SelectedTrack = MediaPlayer->GetSelectedTrack(EMediaPlayerTrack::Video); - const int32 SelectedFormat = MediaPlayer->GetTrackFormat(EMediaPlayerTrack::Video, SelectedTrack); - const float FrameRate = MediaPlayer->GetVideoTrackFrameRate(SelectedTrack, SelectedFormat); - - //Convert using 1001 for DropFrame FrameRate detection - const uint32 Precision = 1001; - PlayerFrameRate = FFrameRate(FMath::RoundToInt(FrameRate * Precision), Precision); - } - else + if (!Player.IsValid()) { UE_LOG(LogTimecodeSynchronizer, Error, TEXT("Current player is invalid.")); MediaPlayer->Close(); diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Private/TimecodeSynchronizer.cpp b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Private/TimecodeSynchronizer.cpp index 456c776695d7..6a115203f827 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Private/TimecodeSynchronizer.cpp +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Private/TimecodeSynchronizer.cpp @@ -9,6 +9,7 @@ #include "ITimeManagementModule.h" #include "Misc/App.h" +#define LOCTEXT_NAMESPACE "TimecodeSynchronizer" /** * FTimecodeSynchronizerActiveTimecodedInputSource @@ -27,16 +28,20 @@ void FTimecodeSynchronizerActiveTimecodedInputSource::ConvertToLocalFrameRate(co UTimecodeSynchronizer::UTimecodeSynchronizer(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) + , bUseCustomTimeStep(false) + , CustomTimeStep(nullptr) , FixedFrameRate(30, 1) + , TimecodeProviderType(ETimecodeSynchronizationTimecodeType::SystemTime) + , TimecodeProvider(nullptr) + , MasterSynchronizationSourceIndex(INDEX_NONE) , PreRollingTimecodeMarginOfErrors(4) , PreRollingTimeout(30.f) - , bUseMasterSynchronizationSource(false) - , MasterSynchronizationSourceIndex(INDEX_NONE) , State(ESynchronizationState::None) , CurrentFrameTime(0) - , CurrentSynchronizedTimecode(FTimecode()) , StartPreRollingTime(0.0) , bRegistered(false) + , PreviousFixedFrameRate(0.f) + , bPreviousUseFixedFrameRate(false) , ActiveMasterSynchronizationTimecodedSourceIndex(INDEX_NONE) { } @@ -52,10 +57,29 @@ void UTimecodeSynchronizer::BeginDestroy() } #if WITH_EDITOR +bool UTimecodeSynchronizer::CanEditChange(const UProperty* InProperty) const +{ + if (!Super::CanEditChange(InProperty)) + { + return false; + } + + if (InProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UTimecodeSynchronizer, TimecodeProvider)) + { + return TimecodeProviderType == ETimecodeSynchronizationTimecodeType::TimecodeProvider; + } + if (InProperty->GetFName() == GET_MEMBER_NAME_CHECKED(UTimecodeSynchronizer, MasterSynchronizationSourceIndex)) + { + return TimecodeProviderType == ETimecodeSynchronizationTimecodeType::InputSource; + } + + return true; +} + void UTimecodeSynchronizer::PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) { - // Make sure the master source index is valid UseAsMasterSynchronizationSource - if (bUseMasterSynchronizationSource) + // Make sure the master source index is valid + if (TimecodeProviderType == ETimecodeSynchronizationTimecodeType::InputSource) { if (!TimeSynchronizationInputSources.IsValidIndex(MasterSynchronizationSourceIndex) || TimeSynchronizationInputSources[MasterSynchronizationSourceIndex] == nullptr @@ -70,20 +94,56 @@ void UTimecodeSynchronizer::PostEditChangeChainProperty(FPropertyChangedChainEve } #endif +FFrameTime UTimecodeSynchronizer::ConvertTimecodeToFrameTime(const FTimecode& InTimecode) const +{ + return InTimecode.ToFrameNumber(GetFrameRate()); +} + +FTimecode UTimecodeSynchronizer::ConvertFrameTimeToTimecode(const FFrameTime& InFFrameTime) const +{ + const bool bIsDropFrame = FTimecode::IsDropFormatTimecodeSupported(GetFrameRate()); + return FTimecode::FromFrameNumber(InFFrameTime.FrameNumber, GetFrameRate(), bIsDropFrame); +} + FTimecode UTimecodeSynchronizer::GetTimecode() const { - return CurrentSynchronizedTimecode; + if(TimecodeProviderType == ETimecodeSynchronizationTimecodeType::InputSource) + { + if(ActiveTimecodedInputSources.IsValidIndex(ActiveMasterSynchronizationTimecodedSourceIndex)) + { + FTimecodeSynchronizerActiveTimecodedInputSource TimecodedInputSource = ActiveTimecodedInputSources[ActiveMasterSynchronizationTimecodedSourceIndex]; + FFrameTime NextSampleTime = TimecodedInputSource.InputSource->GetNextSampleTime(); + if (NextSampleTime != 0) + { + TimecodedInputSource.NextSampleTime = NextSampleTime; + TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); + TimecodedInputSource.ConvertToLocalFrameRate(GetFrameRate()); + } + return ConvertFrameTimeToTimecode(TimecodedInputSource.MaxSampleLocalTime); + } + } + else if(TimecodeProviderType == ETimecodeSynchronizationTimecodeType::TimecodeProvider) + { + if (RegisteredTimecodeProvider) + { + return RegisteredTimecodeProvider->GetTimecode(); + } + } + + FTimecode Result = UTimecodeProvider::GetSystemTimeTimecode(GetFrameRate()); + return Result; } FFrameRate UTimecodeSynchronizer::GetFrameRate() const { - return bUseCustomTimeStep ? CustomTimeStep->FixedFrameRate : FixedFrameRate; + return bUseCustomTimeStep && CustomTimeStep ? CustomTimeStep->FixedFrameRate : FixedFrameRate; } ETimecodeProviderSynchronizationState UTimecodeSynchronizer::GetSynchronizationState() const { switch(State) { + case ESynchronizationState::PreRolling_WaitGenlockTimecodeProvider: case ESynchronizationState::PreRolling_WaitReadiness: case ESynchronizationState::PreRolling_Synchronizing: case ESynchronizationState::PreRolling_Buffering: @@ -99,7 +159,8 @@ ETimecodeProviderSynchronizationState UTimecodeSynchronizer::GetSynchronizationS bool UTimecodeSynchronizer::IsSynchronizing() const { - return State == ESynchronizationState::PreRolling_WaitReadiness + return State == ESynchronizationState::PreRolling_WaitGenlockTimecodeProvider + || State == ESynchronizationState::PreRolling_WaitReadiness || State == ESynchronizationState::PreRolling_Synchronizing || State == ESynchronizationState::PreRolling_Buffering; } @@ -112,38 +173,101 @@ bool UTimecodeSynchronizer::IsSynchronized() const void UTimecodeSynchronizer::Register() { - UTimecodeProvider* Provider = GEngine->GetTimecodeProvider(); - if (Provider == this || Provider == nullptr) - { - bRegistered = GEngine->SetTimecodeProvider(this); - SetTickEnabled(bRegistered); + // Set CustomTimeStep + bRegistered = false; - if (!bRegistered) + if (bUseCustomTimeStep) + { + if (GEngine->GetCustomTimeStep()) { - UE_LOG(LogTimecodeSynchronizer, Error, TEXT("Could not set %s as the Timecode Provider."), *GetName()); + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("Genlock source is already in place.")); + SwitchState(ESynchronizationState::Error); } + + if (!CustomTimeStep) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The Genlock source is not set.")); + SwitchState(ESynchronizationState::Error); + return; + } + + if (!GEngine->SetCustomTimeStep(CustomTimeStep)) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The Genlock source failed to be set on Engine.")); + SwitchState(ESynchronizationState::Error); + return; + } + RegisteredCustomTimeStep = CustomTimeStep; } else { - UE_LOG(LogTimecodeSynchronizer, Error, TEXT("There is already a Timecode Provider in place.")); + PreviousFixedFrameRate = GEngine->FixedFrameRate; + bPreviousUseFixedFrameRate = GEngine->bUseFixedFrameRate; + GEngine->FixedFrameRate = FixedFrameRate.AsDecimal(); + GEngine->bUseFixedFrameRate = true; + } + + // Set TimecodeProvider + if (GEngine->GetTimecodeProvider()) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("A Timecode Provider is already in place.")); SwitchState(ESynchronizationState::Error); } + else if (TimecodeProviderType == ETimecodeSynchronizationTimecodeType::TimecodeProvider) + { + if (!TimecodeProvider) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("TimecodeProvider is not set.")); + SwitchState(ESynchronizationState::Error); + return; + } + + if (!GEngine->SetTimecodeProvider(TimecodeProvider)) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("TimecodeProvider failed to be set on Engine.")); + SwitchState(ESynchronizationState::Error); + return; + } + RegisteredTimecodeProvider = TimecodeProvider; + } + else + { + if (!GEngine->SetTimecodeProvider(this)) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("TimecodeSynchronizer failed to be set as the TimecodeProvider for the Engine.")); + SwitchState(ESynchronizationState::Error); + return; + } + RegisteredTimecodeProvider = this; + } + + bRegistered = true; + SetTickEnabled(bRegistered); } void UTimecodeSynchronizer::Unregister() { UTimecodeProvider* Provider = GEngine->GetTimecodeProvider(); - if (Provider == this) + if (Provider == RegisteredTimecodeProvider) { GEngine->SetTimecodeProvider(nullptr); } - bRegistered = false; + RegisteredTimecodeProvider = nullptr; - IMediaModule* MediaModule = FModuleManager::LoadModulePtr("Media"); - if (MediaModule != nullptr) + UEngineCustomTimeStep* TimeStep = GEngine->GetCustomTimeStep(); + if (TimeStep == RegisteredCustomTimeStep) { - MediaModule->GetOnTickPreEngineCompleted().RemoveAll(this); + GEngine->SetCustomTimeStep(nullptr); } + else if (RegisteredCustomTimeStep == nullptr) + { + GEngine->FixedFrameRate = PreviousFixedFrameRate; + GEngine->bUseFixedFrameRate = bPreviousUseFixedFrameRate; + } + RegisteredCustomTimeStep = nullptr; + + bRegistered = false; + SetTickEnabled(bRegistered); } void UTimecodeSynchronizer::SetTickEnabled(bool bEnabled) @@ -151,7 +275,8 @@ void UTimecodeSynchronizer::SetTickEnabled(bool bEnabled) IMediaModule* MediaModule = FModuleManager::LoadModulePtr("Media"); if (MediaModule == nullptr) { - UE_LOG(LogTimecodeSynchronizer, Error, TEXT("Media module couldn't be loaded")); + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The 'Media' module couldn't be loaded")); + SwitchState(ESynchronizationState::Error); return; } @@ -181,24 +306,13 @@ bool UTimecodeSynchronizer::StartPreRoll() { if (IsSynchronizing() || IsSynchronized()) { - Unregister(); - StopInputSources(); - GEngine->SetCustomTimeStep(nullptr); + UE_LOG(LogTimecodeSynchronizer, Warning, TEXT("Already synchronizing or synchronized.")); return false; } else { - if (bUseCustomTimeStep && CustomTimeStep) - { - const bool success = GEngine->SetCustomTimeStep(CustomTimeStep); - if (!success) - { - UE_LOG(LogTimecodeSynchronizer, Warning, TEXT("CustomTimeStep failed to be set on Engine.")); - return false; - } - } - StopInputSources(); + ActiveMasterSynchronizationTimecodedSourceIndex = INDEX_NONE; // Go through all sources and select usable ones @@ -213,15 +327,7 @@ bool UTimecodeSynchronizer::StartPreRoll() FTimecodeSynchronizerActiveTimecodedInputSource& NewSource = ActiveTimecodedInputSources[NewItemIndex]; NewSource.InputSource = InputSource; - //Stamp source FrameRate for time conversion - NewSource.FrameRate = InputSource->GetFrameRate(); - - if (!NewSource.FrameRate.IsMultipleOf(GetFrameRate()) && !NewSource.FrameRate.IsFactorOf(GetFrameRate())) - { - UE_LOG(LogTimecodeSynchronizer, Warning, TEXT("Source %s doesn't have a frame rate common to TimecodeSynchronizer frame rate."), *NewSource.InputSource->GetDisplayName()) - } - - if (bUseMasterSynchronizationSource && Index == MasterSynchronizationSourceIndex) + if (TimecodeProviderType == ETimecodeSynchronizationTimecodeType::InputSource && Index == MasterSynchronizationSourceIndex) { ActiveMasterSynchronizationTimecodedSourceIndex = NewItemIndex; } @@ -239,7 +345,7 @@ bool UTimecodeSynchronizer::StartPreRoll() } } - if (bUseMasterSynchronizationSource && ActiveMasterSynchronizationTimecodedSourceIndex == INDEX_NONE) + if (TimecodeProviderType == ETimecodeSynchronizationTimecodeType::InputSource && ActiveMasterSynchronizationTimecodedSourceIndex == INDEX_NONE) { UE_LOG(LogTimecodeSynchronizer, Warning, TEXT("The Master Synchronization Source could not be found.")); } @@ -249,19 +355,18 @@ bool UTimecodeSynchronizer::StartPreRoll() Register(); } - //Engage synchronization procedure only if we've successfully registered as the TimecodeProvider + //Engage synchronization procedure only if we've successfully if (bRegistered) { const bool bDoTick = true; - SwitchState(ESynchronizationState::PreRolling_WaitReadiness, bDoTick); + SwitchState(ESynchronizationState::PreRolling_WaitGenlockTimecodeProvider, bDoTick); } else { - //Cleanup CustomTimeStep since we start by setting it - GEngine->SetCustomTimeStep(nullptr); StopInputSources(); - UE_LOG(LogTimecodeSynchronizer, Warning, TEXT("Couldn't start preroll. TimecodeSynchronizer is not registered. (Maybe there is no input sources)")); + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("Couldn't start preroll. TimecodeSynchronizer is not registered. (Maybe there is no input sources)")); + SwitchState(ESynchronizationState::Error); } return bRegistered; @@ -270,6 +375,7 @@ bool UTimecodeSynchronizer::StartPreRoll() void UTimecodeSynchronizer::StopInputSources() { + Unregister(); for (FTimecodeSynchronizerActiveTimecodedInputSource& TimecodedInputSource : ActiveTimecodedInputSources) { if (TimecodedInputSource.InputSource) @@ -286,7 +392,7 @@ void UTimecodeSynchronizer::StopInputSources() } } - SetCurrentFrameTime(FFrameTime(0)); + CurrentFrameTime = FFrameTime(0); ActiveTimecodedInputSources.Reset(); ActiveSynchronizedSources.Reset(); SwitchState(ESynchronizationState::None); @@ -306,10 +412,18 @@ void UTimecodeSynchronizer::SwitchState(const ESynchronizationState NewState, co { break; } - case ESynchronizationState::PreRolling_WaitReadiness: + case ESynchronizationState::PreRolling_WaitGenlockTimecodeProvider: { StartPreRollingTime = FApp::GetCurrentTime(); SynchronizationEvent.Broadcast(ETimecodeSynchronizationEvent::SynchronizationStarted); + if (bDoTick) + { + TickPreRolling_WaitGenlockTimecodeProvider(); + } + break; + } + case ESynchronizationState::PreRolling_WaitReadiness: + { if (bDoTick) { TickPreRolling_WaitReadiness(); @@ -334,6 +448,7 @@ void UTimecodeSynchronizer::SwitchState(const ESynchronizationState NewState, co } case ESynchronizationState::Synchronized: { + bSourceStarted = false; SynchronizationEvent.Broadcast(ETimecodeSynchronizationEvent::SynchronizationSucceeded); if (bDoTick) { @@ -363,6 +478,9 @@ void UTimecodeSynchronizer::Tick_Switch() { switch (State) { + case ESynchronizationState::PreRolling_WaitGenlockTimecodeProvider: + TickPreRolling_WaitGenlockTimecodeProvider(); + break; case ESynchronizationState::PreRolling_WaitReadiness: TickPreRolling_WaitReadiness(); break; @@ -381,8 +499,118 @@ void UTimecodeSynchronizer::Tick_Switch() } } +bool UTimecodeSynchronizer::Tick_TestGenlock() +{ + if (bUseCustomTimeStep) + { + if (RegisteredCustomTimeStep == nullptr) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The registered Genlock source is invalid.")); + SwitchState(ESynchronizationState::Error); + return false; + } + + if (GEngine->GetCustomTimeStep() != RegisteredCustomTimeStep) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The registered Genlock source is not the Engine CustomTimeStep.")); + SwitchState(ESynchronizationState::Error); + return false; + } + + const ECustomTimeStepSynchronizationState SynchronizationState = RegisteredCustomTimeStep->GetSynchronizationState(); + + if (SynchronizationState != ECustomTimeStepSynchronizationState::Synchronized && SynchronizationState != ECustomTimeStepSynchronizationState::Synchronizing) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The Genlock source stopped while synchronizing.")); + SwitchState(ESynchronizationState::Error); + return false; + } + + return SynchronizationState == ECustomTimeStepSynchronizationState::Synchronized; + } + return true; +} + +bool UTimecodeSynchronizer::Tick_TestTimecode() +{ + if (TimecodeProviderType == ETimecodeSynchronizationTimecodeType::TimecodeProvider) + { + if (RegisteredTimecodeProvider == nullptr) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The registered TimecodeProvider is invalid.")); + SwitchState(ESynchronizationState::Error); + return false; + } + + if (GEngine->GetTimecodeProvider() != RegisteredTimecodeProvider) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The registered TimecodeProvider is not the Engine TimecodeProvider.")); + SwitchState(ESynchronizationState::Error); + return false; + } + + const ETimecodeProviderSynchronizationState SynchronizationState = RegisteredTimecodeProvider->GetSynchronizationState(); + + if (SynchronizationState != ETimecodeProviderSynchronizationState::Synchronized && SynchronizationState != ETimecodeProviderSynchronizationState::Synchronizing) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The TimecodeProvider stopped while synchronizing.")); + SwitchState(ESynchronizationState::Error); + return false; + } + + if (SynchronizationState == ETimecodeProviderSynchronizationState::Synchronized) + { + if (RegisteredTimecodeProvider->GetFrameRate() != GetFrameRate()) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The TimecodeProvider frame rate do not correspond to the specified frame rate.")); + SwitchState(ESynchronizationState::Error); + } + } + + return SynchronizationState == ETimecodeProviderSynchronizationState::Synchronized; + } + else if (TimecodeProviderType == ETimecodeSynchronizationTimecodeType::InputSource) + { + if (!ActiveTimecodedInputSources.IsValidIndex(ActiveMasterSynchronizationTimecodedSourceIndex)) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The InputSource '%d' that we try to synchronize on is not valid."), ActiveMasterSynchronizationTimecodedSourceIndex); + SwitchState(ESynchronizationState::Error); + return false; + } + + if (ActiveTimecodedInputSources[ActiveMasterSynchronizationTimecodedSourceIndex].InputSource == nullptr) + { + UE_LOG(LogTimecodeSynchronizer, Error, TEXT("The InputSource '%d' doesn't have an input source."), ActiveMasterSynchronizationTimecodedSourceIndex); + SwitchState(ESynchronizationState::Error); + return false; + } + + return ActiveTimecodedInputSources[ActiveMasterSynchronizationTimecodedSourceIndex].InputSource->IsReady(); + } + return true; +} + +void UTimecodeSynchronizer::TickPreRolling_WaitGenlockTimecodeProvider() +{ + const bool bCustomTimeStepReady = Tick_TestGenlock(); + const bool bTimecodeProvider = Tick_TestTimecode(); + + if (bCustomTimeStepReady && bTimecodeProvider) + { + const bool bDoTick = true; + SwitchState(ESynchronizationState::PreRolling_WaitReadiness, bDoTick); + } +} + void UTimecodeSynchronizer::TickPreRolling_WaitReadiness() { + const bool bCustomTimeStepReady = Tick_TestGenlock(); + const bool bTimecodeProvider = Tick_TestTimecode(); + if (!bCustomTimeStepReady || !bTimecodeProvider) + { + return; + } + bool bAllSourceAreReady = true; for (FTimecodeSynchronizerActiveTimecodedInputSource& TimecodedInputSource : ActiveTimecodedInputSources) { @@ -393,8 +621,21 @@ void UTimecodeSynchronizer::TickPreRolling_WaitReadiness() { if (bIsReady) { + check(TimecodedInputSource.InputSource); + TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); bIsReady = bIsReady && TimecodedInputSource.AvailableSampleCount > 0; + + if (!TimecodedInputSource.bIsReady && bIsReady) + { + //Stamp source FrameRate for time conversion + TimecodedInputSource.FrameRate = TimecodedInputSource.InputSource->GetFrameRate(); + if (!TimecodedInputSource.FrameRate.IsMultipleOf(GetFrameRate()) && !TimecodedInputSource.FrameRate.IsFactorOf(GetFrameRate())) + { + UE_LOG(LogTimecodeSynchronizer, Warning, TEXT("Source %s doesn't have a frame rate common to TimecodeSynchronizer frame rate."), *TimecodedInputSource.InputSource->GetDisplayName()) + } + } + TimecodedInputSource.bIsReady = bIsReady; } } @@ -411,16 +652,27 @@ void UTimecodeSynchronizer::TickPreRolling_WaitReadiness() void UTimecodeSynchronizer::TickPreRolling_Synchronizing() { + const bool bCustomTimeStepReady = Tick_TestGenlock(); + const bool bTimecodeProvider = Tick_TestTimecode(); + if (!bCustomTimeStepReady || !bTimecodeProvider) + { + return; + } + // Fetch each sources samples time and early exit if a source isn`t ready for (FTimecodeSynchronizerActiveTimecodedInputSource& TimecodedInputSource : ActiveTimecodedInputSources) { check(TimecodedInputSource.InputSource); - const bool bIsReady = TimecodedInputSource.InputSource->IsReady(); - TimecodedInputSource.NextSampleTime = TimecodedInputSource.InputSource->GetNextSampleTime(); - TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); - TimecodedInputSource.ConvertToLocalFrameRate(GetFrameRate()); + FFrameTime NextSampleTime = TimecodedInputSource.InputSource->GetNextSampleTime(); + if (NextSampleTime != 0) + { + TimecodedInputSource.NextSampleTime = NextSampleTime; + TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); + TimecodedInputSource.ConvertToLocalFrameRate(GetFrameRate()); + } + const bool bIsReady = TimecodedInputSource.InputSource->IsReady(); if (!bIsReady) { UE_LOG(LogTimecodeSynchronizer, Error, TEXT("Source '%s' stopped while synchronizing."), *TimecodedInputSource.InputSource->GetDisplayName()); @@ -429,40 +681,7 @@ void UTimecodeSynchronizer::TickPreRolling_Synchronizing() } } - // Find the synchronization time that matches for all active sources. - // If a master source is selected, it forces the selected Timecode is simply fetched from it. - bool bFoundTimecode = false; - FFrameTime NewSynchronizedTime; - if (ActiveTimecodedInputSources.IsValidIndex(ActiveMasterSynchronizationTimecodedSourceIndex)) - { - const FTimecodeSynchronizerActiveTimecodedInputSource& TimecodedInputSource = ActiveTimecodedInputSources[ActiveMasterSynchronizationTimecodedSourceIndex]; - if (TimecodedInputSource.AvailableSampleCount > 0) - { - NewSynchronizedTime = TimecodedInputSource.NextSampleLocalTime; - bFoundTimecode = true; - } - } - else - { - // Loop to find the next sample not yet processed by all sources - check(ActiveTimecodedInputSources.Num() > 0); - NewSynchronizedTime = ActiveTimecodedInputSources[0].NextSampleLocalTime; - for (FTimecodeSynchronizerActiveTimecodedInputSource& TimecodedInputSource : ActiveTimecodedInputSources) - { - if (TimecodedInputSource.AvailableSampleCount > 0) - { - NewSynchronizedTime = FMath::Max(NewSynchronizedTime, TimecodedInputSource.NextSampleLocalTime); - bFoundTimecode = true; - } - } - } - - if (bFoundTimecode == false) - { - UE_LOG(LogTimecodeSynchronizer, Error, TEXT("No initial Timecode was found.")); - SwitchState(ESynchronizationState::Error); - return; - } + FFrameTime NewSynchronizedTime = ConvertTimecodeToFrameTime(GetTimecode()); // Check if all inputs have that valid FrameTime bool bDoContains = true; @@ -489,7 +708,7 @@ void UTimecodeSynchronizer::TickPreRolling_Synchronizing() if (bDoContains) { - SetCurrentFrameTime(NewSynchronizedTime); + CurrentFrameTime = NewSynchronizedTime; const bool bDoTick = true; SwitchState(ESynchronizationState::PreRolling_Buffering, bDoTick); @@ -498,6 +717,13 @@ void UTimecodeSynchronizer::TickPreRolling_Synchronizing() void UTimecodeSynchronizer::TickPreRolling_Buffering() { + const bool bCustomTimeStepReady = Tick_TestGenlock(); + const bool bTimecodeProvider = Tick_TestTimecode(); + if (!bCustomTimeStepReady || !bTimecodeProvider) + { + return; + } + // Wait for all the NumberOfExtraBufferedFrame bool bAllBuffered = true; for (FTimecodeSynchronizerActiveTimecodedInputSource& TimecodedInputSource : ActiveTimecodedInputSources) @@ -505,11 +731,15 @@ void UTimecodeSynchronizer::TickPreRolling_Buffering() check(TimecodedInputSource.InputSource); if (TimecodedInputSource.InputSource->NumberOfExtraBufferedFrame > 0) { - bool bIsReady = TimecodedInputSource.InputSource->IsReady(); - TimecodedInputSource.NextSampleTime = TimecodedInputSource.InputSource->GetNextSampleTime(); - TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); - TimecodedInputSource.ConvertToLocalFrameRate(GetFrameRate()); + FFrameTime NextSampleTime = TimecodedInputSource.InputSource->GetNextSampleTime(); + if (NextSampleTime != 0) + { + TimecodedInputSource.NextSampleTime = NextSampleTime; + TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); + TimecodedInputSource.ConvertToLocalFrameRate(GetFrameRate()); + } + bool bIsReady = TimecodedInputSource.InputSource->IsReady(); if (bIsReady && TimecodedInputSource.AvailableSampleCount > 0) { //Count buffered frame from the selected start time and not from this source next sample time @@ -534,11 +764,7 @@ void UTimecodeSynchronizer::TickPreRolling_Buffering() { const bool bCanProceed = AreSourcesReady(); if (bCanProceed) - { - StartSources(); - - UE_LOG(LogTimecodeSynchronizer, Log, TEXT("TimecodeProvider synchronized at %s"), *CurrentSynchronizedTimecode.ToString()); - + { const bool bDoTick = false; SwitchState(ESynchronizationState::Synchronized, bDoTick); } @@ -547,35 +773,32 @@ void UTimecodeSynchronizer::TickPreRolling_Buffering() void UTimecodeSynchronizer::TickSynchronized() { - FFrameTime NewFrameTime = CurrentFrameTime + GetFrameRate().AsFrameTime(FApp::GetDeltaTime()); - - if (ActiveTimecodedInputSources.IsValidIndex(ActiveMasterSynchronizationTimecodedSourceIndex)) + const bool bCustomTimeStepReady = Tick_TestGenlock(); + const bool bTimecodeProvider = Tick_TestTimecode(); + if (!bCustomTimeStepReady || !bTimecodeProvider) { - FTimecodeSynchronizerActiveTimecodedInputSource& TimecodedInputSource = ActiveTimecodedInputSources[ActiveMasterSynchronizationTimecodedSourceIndex]; - TimecodedInputSource.NextSampleTime = TimecodedInputSource.InputSource->GetNextSampleTime(); - TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); - TimecodedInputSource.ConvertToLocalFrameRate(GetFrameRate()); - - if (NewFrameTime > TimecodedInputSource.MaxSampleLocalTime) - { - UE_LOG(LogTimecodeSynchronizer, Warning, TEXT("Current Timecode went beyond the master source maximum Timecode. Consider adding more buffer.")); - NewFrameTime = TimecodedInputSource.MaxSampleLocalTime; - } - else if (NewFrameTime < TimecodedInputSource.NextSampleLocalTime) - { - UE_LOG(LogTimecodeSynchronizer, Warning, TEXT("Current Timecode went below the master source maximum Timecode. Is FrameRate too slow?")); - NewFrameTime = TimecodedInputSource.NextSampleLocalTime; - }; + return; } - SetCurrentFrameTime(NewFrameTime); + if (!bSourceStarted) + { + StartSources(); + bSourceStarted = true; + UE_LOG(LogTimecodeSynchronizer, Log, TEXT("TimecodeProvider synchronized at %s"), *FApp::GetTimecode().ToString()); + } + + CurrentFrameTime = ConvertTimecodeToFrameTime(FApp::GetTimecode()); // Test if all sources have the frame for (FTimecodeSynchronizerActiveTimecodedInputSource& TimecodedInputSource : ActiveTimecodedInputSources) { - TimecodedInputSource.NextSampleTime = TimecodedInputSource.InputSource->GetNextSampleTime(); - TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); - TimecodedInputSource.ConvertToLocalFrameRate(GetFrameRate()); + FFrameTime NextSampleTime = TimecodedInputSource.InputSource->GetNextSampleTime(); + if (NextSampleTime != 0) + { + TimecodedInputSource.NextSampleTime = NextSampleTime; + TimecodedInputSource.AvailableSampleCount = TimecodedInputSource.InputSource->GetAvailableSampleCount(); + TimecodedInputSource.ConvertToLocalFrameRate(GetFrameRate()); + } const bool bIsReady = TimecodedInputSource.InputSource->IsReady(); if (bIsReady) @@ -596,9 +819,7 @@ void UTimecodeSynchronizer::TickSynchronized() void UTimecodeSynchronizer::EnterStateError() { - Unregister(); StopInputSources(); - GEngine->SetCustomTimeStep(nullptr); SynchronizationEvent.Broadcast(ETimecodeSynchronizationEvent::SynchronizationFailed); } @@ -607,13 +828,6 @@ void UTimecodeSynchronizer::TickError() } -void UTimecodeSynchronizer::SetCurrentFrameTime(const FFrameTime& InNewTime) -{ - CurrentFrameTime = InNewTime; - const bool bIsDropFrame = FTimecode::IsDropFormatTimecodeSupported(GetFrameRate()); - CurrentSynchronizedTimecode = FTimecode::FromFrameNumber(CurrentFrameTime.FrameNumber, GetFrameRate(), bIsDropFrame); -} - bool UTimecodeSynchronizer::AreSourcesReady() const { for (const FTimecodeSynchronizerActiveTimecodedInputSource& InputSource : ActiveTimecodedInputSources) @@ -648,5 +862,4 @@ void UTimecodeSynchronizer::StartSources() } } - - +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Public/MediaPlayerInputSource.h b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Public/MediaPlayerInputSource.h index 552e9a3164d3..ed7ab5873c21 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Public/MediaPlayerInputSource.h +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Public/MediaPlayerInputSource.h @@ -38,9 +38,6 @@ public: UPROPERTY(EditAnywhere, Category = Player) UMediaTexture* MediaTexture; -private: - FFrameRate PlayerFrameRate; - public: //~ Begin UObject Interface diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Public/TimecodeSynchronizer.h b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Public/TimecodeSynchronizer.h index 6c58e8ea1c90..c6da278e2c6c 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Public/TimecodeSynchronizer.h +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizer/Public/TimecodeSynchronizer.h @@ -11,6 +11,7 @@ class UFixedFrameRateCustomTimeStep; +class UTimeSynchronizationSource; USTRUCT() @@ -61,6 +62,22 @@ struct FTimecodeSynchronizerActiveTimecodedInputSource }; +/** + * Enumerates Timecode source type. + */ +UENUM() +enum class ETimecodeSynchronizationTimecodeType +{ + /** Use an external Timecode Provider to provide the timecode to follow. */ + TimecodeProvider, + + /** Use one of the InputSource as the Timecode Provider. */ + InputSource, + + /** Use one of the SystemTime as the Timecode Provider. */ + SystemTime, +}; + /** * Enumerates Synchronization related events. */ @@ -88,6 +105,7 @@ public: //~ Begin UObject Interface virtual void BeginDestroy() override; #if WITH_EDITOR + virtual bool CanEditChange(const UProperty* InProperty) const override; virtual void PostEditChangeChainProperty(FPropertyChangedChainEvent& PropertyChangedEvent) override; #endif //~ End UObject Interface @@ -134,7 +152,8 @@ private: enum class ESynchronizationState : uint8 { None, - PreRolling_WaitReadiness, // wait for all source to be bReady + PreRolling_WaitGenlockTimecodeProvider, // wait for the TimecodeProvider & CustomTimeStep to be Ready + PreRolling_WaitReadiness, // wait for all source to be Ready PreRolling_Synchronizing, // wait and find a valid Timecode to start with PreRolling_Buffering, // make sure each source have a big enough buffer Synchronized, // all sources are running and synchronized @@ -159,7 +178,14 @@ private: /** Switches on current state and ticks it */ void Tick_Switch(); - + + /** Test if the genlock & timecode provider are properly setup */ + bool Tick_TestGenlock(); + bool Tick_TestTimecode(); + + /** Process PreRolling_WaitGenlockTimecodeProvider state */ + void TickPreRolling_WaitGenlockTimecodeProvider(); + /** Process PreRolling_WaitReadiness state */ void TickPreRolling_WaitReadiness(); @@ -184,13 +210,14 @@ private: /** Unregister TimecodeSynchronizer as the TimecodeProvider */ void Unregister(); - /** Unregister TimecodeSynchronizer as the TimecodeProvider */ - void SetCurrentFrameTime(const FFrameTime& InNewTime); + /** Convert Timecode to a FrameTime */ + FFrameTime ConvertTimecodeToFrameTime(const FTimecode& InTimecode) const; + FTimecode ConvertFrameTimeToTimecode(const FFrameTime& InFFrameTime) const; - /* Verify if all sources are ready */ + /** Verify if all sources are ready */ bool AreSourcesReady() const; - /* Start all sources once we're ready to advance time */ + /** Start all sources once we're ready to advance time */ void StartSources(); @@ -201,12 +228,29 @@ public: /** Custom strategy to tick in a interval. */ UPROPERTY(EditAnywhere, Instanced, Category="Genlock", meta=(EditCondition="bUseCustomTimeStep", DisplayName="Genlock Source")) - class UFixedFrameRateCustomTimeStep* CustomTimeStep; + UFixedFrameRateCustomTimeStep* CustomTimeStep; /** The fixed framerate to use. */ UPROPERTY(EditAnywhere, Category="Genlock", meta=(EditCondition="!bUseCustomTimeStep", ClampMin="15.0")) FFrameRate FixedFrameRate; +public: + /** Use a Timecode Provider. */ + UPROPERTY(EditAnywhere, Category="Timecode Provider", meta=(DisplayName="Select")) + ETimecodeSynchronizationTimecodeType TimecodeProviderType; + + /** Custom strategy to tick in a interval. */ + UPROPERTY(EditAnywhere, Instanced, Category="Timecode Provider", meta=(EditCondition="IN_CPP", DisplayName="Timecode Source")) + UTimecodeProvider* TimecodeProvider; + + /** + * Index of the source that drives the synchronized Timecode. + * The source need to be timecoded and flag as bUseForSynchronization + */ + UPROPERTY(EditAnywhere, Category="Timecode Provider", meta=(EditCondition="IN_CPP")) + int32 MasterSynchronizationSourceIndex; + +public: /** Enable verification of margin between synchronized time and source time */ UPROPERTY() bool bUsePreRollingTimecodeMarginOfErrors; @@ -220,19 +264,10 @@ public: bool bUsePreRollingTimeout; /** How long to wait for all source to be ready */ - UPROPERTY(EditAnywhere, Category=Synchronization, meta=(EditCondition="bUsePreRollingTimeout", ClampMin="0.0")) + UPROPERTY(EditAnywhere, Category="Synchronization", meta=(EditCondition="bUsePreRollingTimeout", ClampMin="0.0")) float PreRollingTimeout; - UPROPERTY() - bool bUseMasterSynchronizationSource; - - /** - * Index of the source that drives the synchronized Timecode. - * The source need to be timecoded and flag as bUseForSynchronization - */ - UPROPERTY(EditAnywhere, Category="Input", meta=(EditCondition="bUseMasterSynchronizationSource")) - int32 MasterSynchronizationSourceIndex; - +public: /** Array of all the sources that wants to be synchronized*/ UPROPERTY(EditAnywhere, Instanced, Category="Input") TArray TimeSynchronizationInputSources; @@ -247,22 +282,28 @@ private: UPROPERTY(VisibleAnywhere, Category=Debug) TArray ActiveSynchronizedSources; + UPROPERTY(Transient) + UFixedFrameRateCustomTimeStep* RegisteredCustomTimeStep; + + UPROPERTY(Transient) + UTimecodeProvider* RegisteredTimecodeProvider; + private: /** The actual synchronization state */ ESynchronizationState State; + bool bSourceStarted; /** Current FrameTime of the system */ FFrameTime CurrentFrameTime; - /** Current Timecode of the system */ - FTimecode CurrentSynchronizedTimecode; - /** Timestamp when PreRolling has started */ double StartPreRollingTime; /** Whether or not we are registered as the TimecodeProvider */ bool bRegistered; + float PreviousFixedFrameRate; + bool bPreviousUseFixedFrameRate; /** Index of the active source that drives the synchronized Timecode*/ int32 ActiveMasterSynchronizationTimecodedSourceIndex; diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/AssetEditor/TimecodeSynchronizerEditorToolkit.cpp b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/AssetEditor/TimecodeSynchronizerEditorToolkit.cpp index 0d5a84d1a87c..c3a41b2cba56 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/AssetEditor/TimecodeSynchronizerEditorToolkit.cpp +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/AssetEditor/TimecodeSynchronizerEditorToolkit.cpp @@ -11,8 +11,13 @@ #include "IDetailsView.h" #include "Modules/ModuleManager.h" #include "PropertyEditorModule.h" +#include "Framework/Notifications/NotificationManager.h" #include "Framework/MultiBox/MultiBoxBuilder.h" +#include "Widgets/Colors/SColorBlock.h" #include "Widgets/Docking/SDockTab.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Widgets/Notifications/SNotificationList.h" +#include "Widgets/Notifications/SProgressBar.h" #define LOCTEXT_NAMESPACE "TimecodeSynchronizerEditor" @@ -31,7 +36,7 @@ TSharedRef FTimecodeSynchronizerEditorToolki return NewEditor; } -void FTimecodeSynchronizerEditorToolkit::InitTimecodeSynchronizerEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UTimecodeSynchronizer* Table) +void FTimecodeSynchronizerEditorToolkit::InitTimecodeSynchronizerEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UTimecodeSynchronizer* InTimecodeSynchronizer) { FEditorDelegates::OnAssetPostImport.AddRaw(this, &FTimecodeSynchronizerEditorToolkit::HandleAssetPostImport); @@ -43,7 +48,8 @@ void FTimecodeSynchronizerEditorToolkit::InitTimecodeSynchronizerEditor(const ET const TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout(TimecodeSynchronizerEditorToolkit::Layout) ->AddArea ( - FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical) + FTabManager::NewPrimaryArea() + ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() @@ -54,38 +60,44 @@ void FTimecodeSynchronizerEditorToolkit::InitTimecodeSynchronizerEditor(const ET ->Split ( FTabManager::NewSplitter() - ->SetSizeCoefficient(0.5f) + ->SetSizeCoefficient(0.9f) + ->SetOrientation(Orient_Horizontal) ->Split ( - // Source display - FTabManager::NewStack() - ->SetHideTabWell(true) - ->AddTab(TimecodeSynchronizerEditorToolkit::SourceViewerTabId, ETabState::OpenedTab) - ) - ) - ->Split - ( - FTabManager::NewSplitter() - ->SetSizeCoefficient(0.4f) - ->Split - ( - FTabManager::NewStack() - ->AddTab(TimecodeSynchronizerEditorToolkit::PropertiesTabId, ETabState::OpenedTab) + FTabManager::NewSplitter() + ->SetSizeCoefficient(0.5f) + ->Split + ( + // Source display + FTabManager::NewStack() + ->SetHideTabWell(true) + ->AddTab(TimecodeSynchronizerEditorToolkit::SourceViewerTabId, ETabState::OpenedTab) + ) + ->Split + ( + FTabManager::NewSplitter() + ->SetSizeCoefficient(0.4f) + ->Split + ( + FTabManager::NewStack() + ->AddTab(TimecodeSynchronizerEditorToolkit::PropertiesTabId, ETabState::OpenedTab) + ) + ) ) ) ); - BindCommands(); + InTimecodeSynchronizer->OnSynchronizationEvent().AddSP(this, &FTimecodeSynchronizerEditorToolkit::HandleSynchronizationEvent); const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; - Super::InitAssetEditor(Mode, InitToolkitHost, TimecodeSynchronizerEditorToolkit::AppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, Table); + Super::InitAssetEditor(Mode, InitToolkitHost, TimecodeSynchronizerEditorToolkit::AppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, InTimecodeSynchronizer); ExtendToolBar(); // Get the list of objects to edit the details of TArray ObjectsToEditInDetailsView; - ObjectsToEditInDetailsView.Add(Table); + ObjectsToEditInDetailsView.Add(InTimecodeSynchronizer); // Ensure all objects are transactable for undo/redo in the details panel for (UObject* ObjectToEditInDetailsView : ObjectsToEditInDetailsView) @@ -97,12 +109,28 @@ void FTimecodeSynchronizerEditorToolkit::InitTimecodeSynchronizerEditor(const ET { // Make sure details window is pointing to our object DetailsView->SetObjects(ObjectsToEditInDetailsView); + DetailsView->SetIsPropertyEditingEnabledDelegate + ( + FIsPropertyEditingEnabled::CreateLambda([&] + { + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + ETimecodeProviderSynchronizationState State = Asset->GetSynchronizationState(); + return State == ETimecodeProviderSynchronizationState::Closed; + } + return false; + }) + ); } } FTimecodeSynchronizerEditorToolkit::~FTimecodeSynchronizerEditorToolkit() { FEditorDelegates::OnAssetPostImport.RemoveAll(this); + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + Asset->OnSynchronizationEvent().RemoveAll(this); + } } FName FTimecodeSynchronizerEditorToolkit::GetToolkitFName() const @@ -177,7 +205,51 @@ TSharedRef FTimecodeSynchronizerEditorToolkit::SpawnPropertiesTab(cons .Label(LOCTEXT("GenericDetailsTitle", "Details")) .TabColorScale(GetTabColorScale()) [ - DetailsView.ToSharedRef() + SNew(SVerticalBox) + + SVerticalBox::Slot() + .AutoHeight() + .Padding(0.0f, 0.0f) + .HAlign(HAlign_Fill) + [ + SNew(SOverlay) + + SOverlay::Slot() + [ + SNew(SProgressBar) + .ToolTipText(LOCTEXT("BufferingTooltip", "Buffering...")) + .Visibility_Lambda([this]() -> EVisibility + { + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + ETimecodeProviderSynchronizationState State = Asset->GetSynchronizationState(); + return State == ETimecodeProviderSynchronizationState::Synchronizing ? EVisibility::Visible : EVisibility::Hidden; + } + return EVisibility::Hidden; + }) + ] + + SOverlay::Slot() + [ + SNew(SColorBlock) + .Color(this, &FTimecodeSynchronizerEditorToolkit::GetProgressColor) + .IgnoreAlpha(true) + .Visibility_Lambda([this]() -> EVisibility + { + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + ETimecodeProviderSynchronizationState State = Asset->GetSynchronizationState(); + return State == ETimecodeProviderSynchronizationState::Synchronizing ? EVisibility::Hidden : EVisibility::Visible; + } + return EVisibility::Hidden; + }) + ] + ] + + SVerticalBox::Slot() + .FillHeight(1.0f) + .Padding(0.0f, 0.0f) + .HAlign(HAlign_Fill) + .VAlign(VAlign_Fill) + [ + DetailsView.ToSharedRef() + ] ]; } @@ -207,29 +279,6 @@ void FTimecodeSynchronizerEditorToolkit::HandleAssetPostImport(UFactory* InFacto } } -void FTimecodeSynchronizerEditorToolkit::BindCommands() -{ - ToolkitCommands->MapAction( - FTimecodeSynchronizerEditorCommand::Get().PreRollCommand, - FExecuteAction::CreateLambda([&]() - { - if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) - { - Asset->StartPreRoll(); - } - }), - FCanExecuteAction(), - FIsActionChecked::CreateLambda([&]() - { - if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) - { - ETimecodeProviderSynchronizationState State = Asset->GetSynchronizationState(); - return State == ETimecodeProviderSynchronizationState::Synchronized || State == ETimecodeProviderSynchronizationState::Synchronizing; - } - return false; - })); -} - void FTimecodeSynchronizerEditorToolkit::ExtendToolBar() { TSharedPtr ToolbarExtender = MakeShareable(new FExtender); @@ -242,11 +291,55 @@ void FTimecodeSynchronizerEditorToolkit::ExtendToolBar() ToolbarBuilder.BeginSection("TimecodeSynchronizer"); { ToolbarBuilder.AddToolBarButton( - FTimecodeSynchronizerEditorCommand::Get().PreRollCommand, + FUIAction( + FExecuteAction::CreateLambda([&]() + { + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + Asset->StartPreRoll(); + } + }), + FCanExecuteAction::CreateLambda([&]() + { + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + ETimecodeProviderSynchronizationState State = Asset->GetSynchronizationState(); + return State == ETimecodeProviderSynchronizationState::Closed || State == ETimecodeProviderSynchronizationState::Error; + } + return false; + }), + FIsActionChecked() + ), NAME_None, - TAttribute(), - TAttribute(), - FSlateIcon(FTimecodeSynchronizerEditorStyle::GetStyleSetName(), TEXT("Play")) + LOCTEXT("PreRoll", "Start Synchronization"), + LOCTEXT("PreRoll_ToolTip", "Start all medias and synchronize them."), + FSlateIcon(FTimecodeSynchronizerEditorStyle::GetStyleSetName(), "Synchronized") + ); + + ToolbarBuilder.AddToolBarButton( + FUIAction( + FExecuteAction::CreateLambda([&]() + { + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + Asset->StopInputSources(); + } + }), + FCanExecuteAction::CreateLambda([&]() + { + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + ETimecodeProviderSynchronizationState State = Asset->GetSynchronizationState(); + return State == ETimecodeProviderSynchronizationState::Synchronizing || State == ETimecodeProviderSynchronizationState::Synchronized; + } + return false; + }), + FIsActionChecked() + ), + NAME_None, + LOCTEXT("StopPreRoll", "Stop Synchronization"), + LOCTEXT("StopPreRoll_ToolTip", "Stop all medias and remove the genlock (if enabled)."), + FSlateIcon(FTimecodeSynchronizerEditorStyle::GetStyleSetName(), "Stop") ); } ToolbarBuilder.EndSection(); @@ -257,4 +350,50 @@ void FTimecodeSynchronizerEditorToolkit::ExtendToolBar() RegenerateMenusAndToolbars(); } +FLinearColor FTimecodeSynchronizerEditorToolkit::GetProgressColor() const +{ + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + ETimecodeProviderSynchronizationState State = Asset->GetSynchronizationState(); + switch(State) + { + case ETimecodeProviderSynchronizationState::Error: + return FColor::Red; + case ETimecodeProviderSynchronizationState::Closed: + return FColor::Black; + case ETimecodeProviderSynchronizationState::Synchronized: + return FColor::Green; + case ETimecodeProviderSynchronizationState::Synchronizing: + return FColor::Yellow; + } + } + return FColor::Black; +} + +void FTimecodeSynchronizerEditorToolkit::HandleSynchronizationEvent(ETimecodeSynchronizationEvent Event) +{ + if (Event == ETimecodeSynchronizationEvent::SynchronizationFailed) + { + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + FNotificationInfo NotificationInfo(LOCTEXT("FailedError", "Failed to synchronize. Check Output Log for details!")); + + NotificationInfo.ExpireDuration = 2.0f; + FSlateNotificationManager::Get().AddNotification(NotificationInfo)->SetCompletionState(SNotificationItem::CS_Fail); + } + } +} + +void FTimecodeSynchronizerEditorToolkit::RemoveEditingObject(UObject* Object) +{ + if (UTimecodeSynchronizer* Asset = GetTimecodeSynchronizer()) + { + if (Asset == Object) + { + Asset->OnSynchronizationEvent().RemoveAll(this); + } + } + Super::RemoveEditingObject(Object); +} + #undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/AssetEditor/TimecodeSynchronizerEditorToolkit.h b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/AssetEditor/TimecodeSynchronizerEditorToolkit.h index 6c068bece397..38c75e0f43dd 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/AssetEditor/TimecodeSynchronizerEditorToolkit.h +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/AssetEditor/TimecodeSynchronizerEditorToolkit.h @@ -5,6 +5,8 @@ #include "CoreMinimal.h" #include "Toolkits/SimpleAssetEditor.h" +#include "TimecodeSynchronizer.h" + /** Viewer/editor for a TimecodeSynchronizer */ class FTimecodeSynchronizerEditorToolkit : public FAssetEditorToolkit { @@ -12,7 +14,7 @@ private: using Super = FAssetEditorToolkit; public: - static TSharedRef CreateEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, class UTimecodeSynchronizer* InTimecodeSynchronizer); + static TSharedRef CreateEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UTimecodeSynchronizer* InTimecodeSynchronizer); /** * Edits the specified table @@ -21,7 +23,7 @@ public: * @param InitToolkitHost When Mode is WorldCentric, this is the level editor instance to spawn this editor within * @param InTimecodeSynchronizer The TimecodeSynchronizer asset to edit */ - void InitTimecodeSynchronizerEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, class UTimecodeSynchronizer* InTimecodeSynchronizer); + void InitTimecodeSynchronizerEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UTimecodeSynchronizer* InTimecodeSynchronizer); ~FTimecodeSynchronizerEditorToolkit(); /** IToolkit interface */ @@ -30,6 +32,7 @@ public: virtual FText GetToolkitName() const override; virtual FString GetWorldCentricTabPrefix() const override; virtual FLinearColor GetWorldCentricTabColorScale() const override; + virtual void RemoveEditingObject(UObject* Object) override; virtual void RegisterTabSpawners(const TSharedRef& TabManager) override; virtual void UnregisterTabSpawners(const TSharedRef& TabManager) override; @@ -42,9 +45,11 @@ private: TSharedRef SpawnPropertiesTab(const FSpawnTabArgs& Args); TSharedRef SpawnSourceViewerTab(const FSpawnTabArgs& Args); - void BindCommands(); void ExtendToolBar(); + FLinearColor GetProgressColor() const; + void HandleSynchronizationEvent(ETimecodeSynchronizationEvent Event); + private: /** Dockable tab for properties */ TSharedPtr PropertiesTab; diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorCommand.cpp b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorCommand.cpp index f3acadbc7c18..7026efa6d6a4 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorCommand.cpp +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorCommand.cpp @@ -26,8 +26,6 @@ FTimecodeSynchronizerEditorCommand::FTimecodeSynchronizerEditorCommand() void FTimecodeSynchronizerEditorCommand::RegisterCommands() { UI_COMMAND(OpenEditorCommand, "Timecode Synchronizer", "Open TimecodeSynchronizer Editor", EUserInterfaceActionType::Button, FInputChord()); - UI_COMMAND(PreRollCommand, "PreRoll", "Start Synchronization", EUserInterfaceActionType::Button, FInputChord()); - // Action to open the TimecodeSynchronizerEditor CommandActionList = MakeShareable(new FUICommandList); diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorCommand.h b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorCommand.h index cc9774d740a6..4d894338137f 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorCommand.h +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorCommand.h @@ -21,7 +21,6 @@ private: public: TSharedPtr OpenEditorCommand; - TSharedPtr PreRollCommand; TSharedPtr CommandActionList; }; \ No newline at end of file diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorStyle.cpp b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorStyle.cpp index ef8d61ce3189..a68417e091b0 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorStyle.cpp +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/UI/TimecodeSynchronizerEditorStyle.cpp @@ -31,8 +31,10 @@ void FTimecodeSynchronizerEditorStyle::Register() TimecodeSynchronizerStyle::StyleInstance->Set("Console", new IMAGE_BRUSH("Icon_TimecodeSynchronizer_40x", TimecodeSynchronizerStyle::Icon40x40)); TimecodeSynchronizerStyle::StyleInstance->Set("Console.Small", new IMAGE_BRUSH("Icon_TimecodeSynchronizer_20x", TimecodeSynchronizerStyle::Icon20x20)); - TimecodeSynchronizerStyle::StyleInstance->Set("Play", new IMAGE_BRUSH("Icon_Play_40x", TimecodeSynchronizerStyle::Icon40x40)); - TimecodeSynchronizerStyle::StyleInstance->Set("Play.Small", new IMAGE_BRUSH("Icon_Play_40x", TimecodeSynchronizerStyle::Icon20x20)); + TimecodeSynchronizerStyle::StyleInstance->Set("Synchronized", new IMAGE_BRUSH("Icon_Synchronized_40x", TimecodeSynchronizerStyle::Icon40x40)); + TimecodeSynchronizerStyle::StyleInstance->Set("Synchronized.Small", new IMAGE_BRUSH("Icon_Synchronized_40x", TimecodeSynchronizerStyle::Icon20x20)); + TimecodeSynchronizerStyle::StyleInstance->Set("Stop", new IMAGE_BRUSH("Icon_Stop_40x", TimecodeSynchronizerStyle::Icon40x40)); + TimecodeSynchronizerStyle::StyleInstance->Set("Stop.Small", new IMAGE_BRUSH("Icon_Stop_40x", TimecodeSynchronizerStyle::Icon20x20)); FSlateStyleRegistry::RegisterSlateStyle(*TimecodeSynchronizerStyle::StyleInstance.Get()); diff --git a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/Widgets/STimecodeSynchronizerSourceViewport.cpp b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/Widgets/STimecodeSynchronizerSourceViewport.cpp index ac42dc7e5612..1fc3dd8b8099 100644 --- a/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/Widgets/STimecodeSynchronizerSourceViewport.cpp +++ b/Engine/Plugins/Media/TimecodeSynchronizer/Source/TimecodeSynchronizerEditor/Private/Widgets/STimecodeSynchronizerSourceViewport.cpp @@ -4,9 +4,10 @@ #include "TimecodeSynchronizer.h" #include "EditorStyleSet.h" +#include "Engine/Texture.h" #include "Materials/Material.h" #include "Materials/MaterialExpressionTextureSample.h" -#include "Engine/Texture.h" +#include "Misc/App.h" #include "Widgets/SBoxPanel.h" #include "Widgets/Input/SEditableTextBox.h" #include "Widgets/SOverlay.h" @@ -101,8 +102,14 @@ void STimecodeSynchronizerSourceViewport::Construct(const FArguments& InArgs, UT .ClearKeyboardFocusOnCommit(true) .HintText(LOCTEXT("SourceTextBoxHint", "Source Name")) .IsReadOnly(true) - .Text_Lambda([Name = GetAttachedSource()->InputSource->GetDisplayName()]() -> FText { - return FText::FromString(Name); + .Text_Lambda([&]() -> FText + { + const FTimecodeSynchronizerActiveTimecodedInputSource* AttachedSource = GetAttachedSource(); + if (AttachedSource && AttachedSource->bIsReady) + { + return FText::FromString(GetAttachedSource()->InputSource->GetDisplayName()); + } + return FText(); }) ] ] @@ -204,7 +211,7 @@ FText STimecodeSynchronizerSourceViewport::HandleIntervalMinTimecodeText() const { FTimecode Timecode; const FTimecodeSynchronizerActiveTimecodedInputSource* AttachedSource = GetAttachedSource(); - if (AttachedSource) + if (AttachedSource && AttachedSource->bIsReady) { const bool bIsDropFrame = FTimecode::IsDropFormatTimecodeSupported(AttachedSource->FrameRate); Timecode = FTimecode::FromFrameNumber(AttachedSource->NextSampleTime.FrameNumber, AttachedSource->FrameRate, bIsDropFrame); @@ -217,7 +224,7 @@ FText STimecodeSynchronizerSourceViewport::HandleIntervalMaxTimecodeText() const { FTimecode Timecode; const FTimecodeSynchronizerActiveTimecodedInputSource* AttachedSource = GetAttachedSource(); - if (AttachedSource) + if (AttachedSource && AttachedSource->bIsReady) { const FFrameNumber NextFrame = AttachedSource->NextSampleTime.FrameNumber + AttachedSource->AvailableSampleCount; const bool bIsDropFrame = FTimecode::IsDropFormatTimecodeSupported(AttachedSource->FrameRate); @@ -229,19 +236,12 @@ FText STimecodeSynchronizerSourceViewport::HandleIntervalMaxTimecodeText() const FText STimecodeSynchronizerSourceViewport::HandleCurrentTimecodeText() const { - FTimecode Timecode; - if (TimecodeSynchronization) - { - Timecode = TimecodeSynchronization->GetTimecode(); - } - - return FText::FromString(Timecode.ToString()); + return FText::FromString(FApp::GetTimecode().ToString()); } FText STimecodeSynchronizerSourceViewport::HandleIsSourceMasterText() const { FString Role; - const FTimecodeSynchronizerActiveTimecodedInputSource* AttachedSource = GetAttachedSource(); if (TimecodeSynchronization && AttachedSourceIndex != INDEX_NONE && bIsTimecodedSource && TimecodeSynchronization->GetActiveMasterSynchronizationTimecodedSourceIndex() == AttachedSourceIndex) { Role = "Master"; diff --git a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableStaticMeshAdapter.cpp b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableStaticMeshAdapter.cpp index 68934adcc76a..bfcc43125759 100644 --- a/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableStaticMeshAdapter.cpp +++ b/Engine/Plugins/Runtime/EditableMesh/Source/EditableMesh/EditableStaticMeshAdapter.cpp @@ -810,6 +810,11 @@ void UEditableStaticMeshAdapter::OnEndModification( const UEditableMesh* Editabl void UEditableStaticMeshAdapter::OnRebuildRenderMeshFinish( const UEditableMesh* EditableMesh, const bool bRebuildBoundsAndCollision, const bool bIsPreviewRollback ) { + if (!bIsPreviewRollback) + { + StaticMesh->InitResources(); + } + UpdateBounds( EditableMesh, bRebuildBoundsAndCollision ); if( bRebuildBoundsAndCollision ) @@ -821,8 +826,6 @@ void UEditableStaticMeshAdapter::OnRebuildRenderMeshFinish( const UEditableMesh* // 'final' changes to the same mesh later this frame, and we want to avoid updating the GPU resources twice in one frame. if( !bIsPreviewRollback ) { - StaticMesh->InitResources(); - // NOTE: This can call InvalidateLightingCache() on all components using this mesh, causing Modify() to be // called on those components! Just something to be aware of when EndModification() is called within // an undo transaction. diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/MixedRealityCaptureFramework.uplugin b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/MixedRealityCaptureFramework.uplugin index d258ec41e17e..047efe71a611 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/MixedRealityCaptureFramework.uplugin +++ b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/MixedRealityCaptureFramework.uplugin @@ -26,28 +26,12 @@ "Win32", "Linux" ] - }, - { - "Name": "MixedRealityCaptureCalibration", - "Type": "Runtime", - "LoadingPhase": "PostEngineInit", - "WhitelistPlatforms": [ - "Win64", - "Win32", - "Linux" - ] - }, - { - "Name": "OpenCVHelper", - "Type": "Runtime", - "LoadingPhase": "PostEngineInit", - "WhitelistPlatforms": [ - "Win64", - "Win32", - "Linux" - ] } ], "Plugins": [ + { + "Name": "OpenCVLensDistortion", + "Enabled": true + } ] } \ No newline at end of file diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcCalibrationModule.cpp b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcCalibrationModule.cpp deleted file mode 100644 index df3dacfd5978..000000000000 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcCalibrationModule.cpp +++ /dev/null @@ -1,29 +0,0 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. - -#include "IMrcCalibrationModule.h" -#include "Modules/ModuleManager.h" // for IMPLEMENT_MODULE() -#include "HAL/PlatformProcess.h" - -class FMrcCalibrationModule : public IMrcCalibrationModule -{ -public: - FMrcCalibrationModule(); - -public: - //~ IModuleInterface interface - virtual void StartupModule() override; - virtual void ShutdownModule() override; -}; - -FMrcCalibrationModule::FMrcCalibrationModule() -{} - -void FMrcCalibrationModule::StartupModule() -{ -} - -void FMrcCalibrationModule::ShutdownModule() -{ -} - -IMPLEMENT_MODULE(FMrcCalibrationModule, MixedRealityCaptureCalibration); diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcOpenCVCalibrator.cpp b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcOpenCVCalibrator.cpp deleted file mode 100644 index f34ba19b528e..000000000000 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcOpenCVCalibrator.cpp +++ /dev/null @@ -1,207 +0,0 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. - -#include "MrcOpenCVCalibrator.h" -#include "Engine/TextureRenderTarget2D.h" - -#if WITH_OPENCV -OPENCV_INCLUDES_START -#undef check // the check macro causes problems with opencv headers -#include "opencv2/calib3d.hpp" -#include "opencv2/imgproc.hpp" -OPENCV_INCLUDES_END - -using namespace cv; -#endif - - -UMrcOpenCVCalibrator* UMrcOpenCVCalibrator::CreateCalibrator(int32 BoardWidth /*= 7*/, int32 BoardHeight /*= 5*/, float InSquareSize /*= 3.f*/) -{ - UMrcOpenCVCalibrator* Result = NewObject(UMrcOpenCVCalibrator::StaticClass()); - Result->Reset(BoardWidth, BoardHeight, InSquareSize); - return Result; -} - -void UMrcOpenCVCalibrator::Reset(int32 BoardWidth, int32 BoardHeight, float InSquareSize/*=3.f*/) -{ - SquareSize = InSquareSize; -#if WITH_OPENCV - BoardSize = Size(BoardWidth, BoardHeight); - // Assuming the chessboard is at the origin lying flat on the z plane, construct object coordinates for it - BoardPoints.clear(); - BoardPoints.reserve(BoardSize.height * BoardSize.width); - for (int i = 0; i < BoardSize.height; ++i) - for (int j = 0; j < BoardSize.width; ++j) - BoardPoints.push_back(Point3f(float(j*SquareSize), float(i*SquareSize), 0)); - - // Reserve space for a few samples - ImagePoints.clear(); - ImagePoints.reserve(25); -#endif -} - -bool UMrcOpenCVCalibrator::FeedRenderTarget(UTextureRenderTarget2D* TexRT) -{ -#if WITH_OPENCV - if (TexRT) - { - TArray RawData; - FRenderTarget* RenderTarget = TexRT->GameThread_GetRenderTargetResource(); - EPixelFormat Format = TexRT->GetFormat(); - bool bReadSuccess = false; - switch (Format) - { - case PF_FloatRGBA: - { - TArray FloatColors; - bReadSuccess = RenderTarget->ReadFloat16Pixels(FloatColors); - if (bReadSuccess) - { - RawData.AddUninitialized(FloatColors.Num() * 3); // We need to convert float to uint8 - for (int I = 0; I < FloatColors.Num(); I++) - { - // OpenCV expects BGR ordering and we're ignoring the alpha component - RawData[I * 3 + 0] = (uint8)(FloatColors[I].B*255.0f); - RawData[I * 3 + 1] = (uint8)(FloatColors[I].G*255.0f); - RawData[I * 3 + 2] = (uint8)(FloatColors[I].R*255.0f); - } - } - } - break; - case PF_B8G8R8A8: - { - TArray Colors; - bReadSuccess = RenderTarget->ReadPixels(Colors); - if (bReadSuccess) - { - RawData.AddUninitialized(Colors.Num() * 3); - for (int I = 0; I < Colors.Num(); I++) - { - // Strip out the alpha component for OpenCV - RawData[I * 3 + 0] = Colors[I].B; - RawData[I * 3 + 1] = Colors[I].G; - RawData[I * 3 + 2] = Colors[I].R; - } - } - } - break; - } - - if (bReadSuccess == false) // sorry, no bread ;) - { - // Either invalid texture data or unsupported texture format - return false; - } - - Mat Image(TexRT->SizeY, TexRT->SizeX, CV_8UC3, (void*)RawData.GetData()); - return Feed(Image); - } -#endif - return false; -} - -bool UMrcOpenCVCalibrator::FeedImage(const FString& FilePath) -{ -#if WITH_OPENCV - - FTCHARToUTF8 FilePathUtf8(*FilePath); - cv::String CVPath(FilePathUtf8.Get(), FilePathUtf8.Length()); - Mat Image = imread(CVPath); - - - return Feed(Image); -#else - return false; -#endif -} - -#if WITH_OPENCV -bool UMrcOpenCVCalibrator::Feed(const Mat& Image) -{ - std::vector Corners; - Corners.reserve(BoardSize.height * BoardSize.width); - ImageSize = Image.size(); - if (ImageSize.empty()) - { - return false; - } - - bool bFound = findChessboardCorners(Image, BoardSize, Corners, CV_CALIB_CB_ADAPTIVE_THRESH | CV_CALIB_CB_FAST_CHECK | CV_CALIB_CB_NORMALIZE_IMAGE); - if (bFound) - { - Mat Gray; - cvtColor(Image, Gray, CV_BGR2GRAY); - cornerSubPix(Gray, Corners, Size(11, 11), Size(-1, -1), TermCriteria(CV_TERMCRIT_EPS + CV_TERMCRIT_ITER, 30, 0.1)); - ImagePoints.push_back(Corners); - } - return bFound; -} -#endif - -bool UMrcOpenCVCalibrator::CalculateLensParameters(struct FMrcLensDistortion& OutLensDistortion, float& OutHFOV, float& OutVFOV, float& OutAspectRatio, float& OutMarginOfError) -{ -#if WITH_OPENCV - - if (ImagePoints.empty()) - { - return false; - } - - Mat DistCoeffs; - Mat CameraMatrix = Mat::eye(3, 3, CV_64F); - - OutMarginOfError = FLT_MAX; - { - // calibrateCamera returns rotation and translation vectors, even though we don't use them, we need to - // reserve space for them and pass in these arrays by reference. - std::vector Rvecs, Tvecs; - Rvecs.reserve(ImagePoints.size()); - Tvecs.reserve(ImagePoints.size()); - - // calibrateCamera requires object points for each image capture, even though they're all the same object - // (the chessboard) in all cases. - std::vector> ObjectPoints; - ObjectPoints.resize(ImagePoints.size(), BoardPoints); - - OutMarginOfError = (float)calibrateCamera(ObjectPoints, ImagePoints, ImageSize, CameraMatrix, DistCoeffs, Rvecs, Tvecs); - - } - - if (!checkRange(CameraMatrix) || !checkRange(DistCoeffs)) - { - return false; - } - - // Convert the params to a UE4 struct - { - // The DistCoeffs matrix is a one row matrix: - check(DistCoeffs.rows == 1); - OutLensDistortion.K1 = DistCoeffs.at(0); - OutLensDistortion.K2 = DistCoeffs.at(1); - OutLensDistortion.P1 = DistCoeffs.at(2); - OutLensDistortion.P2 = DistCoeffs.at(3); - - // The docs make it sound like third (and fourth and fifth) radial coefficients may be optional - OutLensDistortion.K3 = DistCoeffs.cols >= 5 ? DistCoeffs.at(4) : .0f; - - check(CameraMatrix.rows == 3 && CameraMatrix.cols == 3); - OutLensDistortion.F.X = CameraMatrix.at(0, 0); - OutLensDistortion.F.Y = CameraMatrix.at(1, 1); - OutLensDistortion.C.X = CameraMatrix.at(0, 2); - OutLensDistortion.C.Y = CameraMatrix.at(1, 2); - } - - // Estimate field of view - { - double AspectRatio, FovX, FovY, FocalLength_Unused; - Point2d PrincipalPoint_Unused; - // We pass in zero aperture size as it is unknown. (It is only required for calculating focal length and the principal point) - calibrationMatrixValues(CameraMatrix, ImageSize, 0.0, 0.0, FovX, FovY, FocalLength_Unused, PrincipalPoint_Unused, AspectRatio); - OutHFOV = FovX; - OutVFOV = FovY; - OutAspectRatio = AspectRatio; - } - return true; -#else - return false; -#endif -} diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcOpenCVCalibrator.h b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcOpenCVCalibrator.h deleted file mode 100644 index 8f240ab117cc..000000000000 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Private/MrcOpenCVCalibrator.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. -#pragma once -#include "CoreMinimal.h" -#include "MrcLensDistortion.h" - -#if WITH_OPENCV -#include "OpenCVHelper.h" -OPENCV_INCLUDES_START -#undef check // the check macro causes problems with opencv headers -#include "opencv2/core/core.hpp" -#include "opencv2/imgcodecs.hpp" -OPENCV_INCLUDES_END -#endif -#include "MrcOpenCVCalibrator.generated.h" - -UCLASS(meta = (BlueprintSpawnableComponent)) -class UMrcOpenCVCalibrator : public UObject -{ - GENERATED_BODY() - -private: - /** - * Default constructor for a CV calibration object. - */ - UMrcOpenCVCalibrator(const FObjectInitializer& ObjectInitializer = FObjectInitializer::Get()) - : UObject(ObjectInitializer) - {} -public: - - /** - * @param BoardWidth The width of the checkerboard used to calibrate the camera counted as number of inner edges. - * @param BoardHeight The height of the checkerboard used to calibrate the camera counted as number of inner edges. - * @param InSquareSize The width of each square in (potentially arbitrary) world units. - */ - UFUNCTION(BlueprintCallable, Category = "MixedRealityCapture|LensCalibration") - static UMrcOpenCVCalibrator* CreateCalibrator(int32 BoardWidth = 7, int32 BoardHeight = 5, float InSquareSize = 3.f); - - /** - * @param BoardWidth The width of the checkerboard used to calibrate the camera counted as number of inner edges. - * @param BoardHeight The height of the checkerboard used to calibrate the camera counted as number of inner edges. - * @param InSquareSize The width of each square in (potentially arbitrary) world units. - */ - void Reset(int32 BoardWidth, int32 BoardHeight, float InSquareSize=3.f); - - /** - * Feeds a render target to the calibration. It must contain a checkerboard somewhere in the image. - * The images fed in should come from the same camera. - * @return true if the calibrator found a checkerboard in the image. - */ - UFUNCTION(BlueprintCallable, Category = "MixedRealityCapture|LensCalibration") - bool FeedRenderTarget(class UTextureRenderTarget2D* TexRT); - - /** - * Feeds an image to the calibration. It must contain a checkerboard somewhere in the image. - * The images fed in should come from the same camera. - * @return true if the calibrator found a checkerboard in the image. - */ - UFUNCTION(BlueprintCallable, Category = "MixedRealityCapture|LensCalibration") - bool FeedImage(const FString& FilePath); - - /** - * @param OutLensDistortion the calculated distortion data from the images passed into the calibrator. - * @return true if there was enough data to calculate the distortion - */ - UFUNCTION(BlueprintCallable, Category = "MixedRealityCapture|LensCalibration") - bool CalculateLensParameters(FMrcLensDistortion& LensDistortion, float& HFOV, float& VFOV, float& AspectRatio, float& MarginOfError); - -private: -#if WITH_OPENCV - bool Feed(const cv::Mat& Image); - - std::vector> ImagePoints; - std::vector BoardPoints; - cv::Size ImageSize; - cv::Size BoardSize; -#endif - float SquareSize; -}; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Public/IMrcCalibrationModule.h b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Public/IMrcCalibrationModule.h deleted file mode 100644 index 4463609af255..000000000000 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureCalibration/Public/IMrcCalibrationModule.h +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. - -#pragma once - -#include "CoreMinimal.h" -#include "Modules/ModuleInterface.h" -#include "HAL/IConsoleManager.h" // for TAutoConsoleVariable<> - -class IConsoleVariable; - -/** - * - */ -class IMrcCalibrationModule : public IModuleInterface -{ -public: - /** Virtual destructor. */ - virtual ~IMrcCalibrationModule() {} -}; - diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/MixedRealityCaptureFramework.Build.cs b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/MixedRealityCaptureFramework.Build.cs index f81178edbbf3..04f14ab9489d 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/MixedRealityCaptureFramework.Build.cs +++ b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/MixedRealityCaptureFramework.Build.cs @@ -34,7 +34,8 @@ public class MixedRealityCaptureFramework : ModuleRules "MediaUtils", "RenderCore", "OpenCVHelper", - "OpenCV" + "OpenCV", + "OpenCVLensDistortion", } ); diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MixedRealityCaptureComponent.cpp b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MixedRealityCaptureComponent.cpp index 363760a5e171..ee9c41b60ab1 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MixedRealityCaptureComponent.cpp +++ b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MixedRealityCaptureComponent.cpp @@ -321,8 +321,6 @@ UMixedRealityCaptureComponent::UMixedRealityCaptureComponent(const FObjectInitia // initialize to a "unknown" state - we don't know what this was calibrated at , RelativeOriginType(MRCaptureComponent_Impl::InvalidOriginType) , CalibratedFOV(0.f) - , LensFocalLenRatio(0.f) - , UndistortedFOV(0.f) { const UMrcFrameworkSettings* MrcSettings = GetDefault(); @@ -823,7 +821,7 @@ void UMixedRealityCaptureComponent::SetCaptureDevice(const FMrcVideoCaptureFeedI } //------------------------------------------------------------------------------ -void UMixedRealityCaptureComponent::SetLensDistortionParameters(const FMrcLensDistortion & ModelRef) +void UMixedRealityCaptureComponent::SetLensDistortionParameters(const FOpenCVLensDistortionParameters& ModelRef) { if (ModelRef != LensDistortionParameters) { @@ -903,9 +901,9 @@ float UMixedRealityCaptureComponent::GetDesiredAspectRatio() const } } - if (MRCaptureComponent_Impl::UseUndistortion && MRCaptureComponent_Impl::UseFocalLenAspect && LensDistortionParameters.IsSet() && LensFocalLenRatio > 0) + if (MRCaptureComponent_Impl::UseUndistortion && MRCaptureComponent_Impl::UseFocalLenAspect && !LensDistortionParameters.IsIdentity() && UndistortedCameraInfo.FocalLengthRatio > 0) { - DesiredAspectRatio *= LensFocalLenRatio; + DesiredAspectRatio *= UndistortedCameraInfo.FocalLengthRatio; } return DesiredAspectRatio; @@ -914,13 +912,9 @@ float UMixedRealityCaptureComponent::GetDesiredAspectRatio() const //------------------------------------------------------------------------------ void UMixedRealityCaptureComponent::RefreshDistortionDisplacementMap() { - if (MRCaptureComponent_Impl::UseUndistortion && LensDistortionParameters.IsSet() && TextureTarget) + if (MRCaptureComponent_Impl::UseUndistortion && !LensDistortionParameters.IsIdentity() && TextureTarget) { - // for OpenCv, 0.0 is cropped fully, whereas 1.0 is uncropped - float CroppingInterpoler = 1.f - MRCaptureComponent_Impl::DistortionCroppingAmount; - - float OutVFOV_Unused; - DistortionDisplacementMap = LensDistortionParameters.CreateUndistortUVMap(FIntPoint(TextureTarget->SizeX, TextureTarget->SizeY), CroppingInterpoler, UndistortedFOV, OutVFOV_Unused, LensFocalLenRatio); + DistortionDisplacementMap = LensDistortionParameters.CreateUndistortUVDisplacementMap(FIntPoint(TextureTarget->SizeX, TextureTarget->SizeY), MRCaptureComponent_Impl::DistortionCroppingAmount, UndistortedCameraInfo); } else { @@ -946,9 +940,9 @@ void UMixedRealityCaptureComponent::RefreshFOV() { FOVAngle = MRCaptureComponent_Impl::CaptureFOVOverride; } - else if (MRCaptureComponent_Impl::UseUndistortion && MRCaptureComponent_Impl::UseUndistortedFOV && LensDistortionParameters.IsSet() && UndistortedFOV > 0) + else if (MRCaptureComponent_Impl::UseUndistortion && MRCaptureComponent_Impl::UseUndistortedFOV && !LensDistortionParameters.IsIdentity() && UndistortedCameraInfo.HorizontalFOV > 0) { - FOVAngle = UndistortedFOV; + FOVAngle = UndistortedCameraInfo.HorizontalFOV; } else if (CalibratedFOV > 0) { diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MrcGarbageMatteCaptureComponent.cpp b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MrcGarbageMatteCaptureComponent.cpp index ed02fedd5c8c..faa939f913a1 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MrcGarbageMatteCaptureComponent.cpp +++ b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MrcGarbageMatteCaptureComponent.cpp @@ -36,11 +36,8 @@ UMrcGarbageMatteCaptureComponent::UMrcGarbageMatteCaptureComponent(const FObject //------------------------------------------------------------------------------ void UMrcGarbageMatteCaptureComponent::OnComponentDestroyed(bool bDestroyingHierarchy) { - if (GarbageMatteActor) - { - GarbageMatteActor->Destroy(); - GarbageMatteActor = nullptr; - } + CleanupSpawnedActors(); + GarbageMatteActor = nullptr; Super::OnComponentDestroyed(bDestroyingHierarchy); } @@ -98,6 +95,20 @@ void UMrcGarbageMatteCaptureComponent::GetGarbageMatteData(TArray 0) + { + AMrcGarbageMatteActor* Actor = SpawnedActors[0]; + if (Actor) + { + Actor->Destroy(); + } + SpawnedActors.RemoveAtSwap(0); + } +} + //------------------------------------------------------------------------------ AMrcGarbageMatteActor* UMrcGarbageMatteCaptureComponent::SpawnNewGarbageMatteActor_Implementation(USceneComponent* InTrackingOrigin) { @@ -123,7 +134,9 @@ AMrcGarbageMatteActor* UMrcGarbageMatteCaptureComponent::SpawnNewGarbageMatteAct SpawnedActor->AttachToComponent(InTrackingOrigin, FAttachmentTransformRules::SnapToTargetNotIncludingScale); } NewGarbageMatteActor = CastChecked(SpawnedActor, ECastCheckedType::NullAllowed); + SpawnedActors.Add(NewGarbageMatteActor); } + return NewGarbageMatteActor; } @@ -136,8 +149,15 @@ void UMrcGarbageMatteCaptureComponent::SetGarbageMatteActor(AMrcGarbageMatteActo GarbageMatteActor->GetGarbageMatteData(GarbageMatteData); ShowOnlyActors.Remove(GarbageMatteActor); - GarbageMatteActor->Destroy(); - GarbageMatteActor = nullptr; + + //Verify if the actual GarbageMatteActor has been spawned by us and destroy if its the case. + const int32 FoundIndex = SpawnedActors.Find(GarbageMatteActor); + if (FoundIndex != INDEX_NONE) + { + SpawnedActors.RemoveAtSwap(FoundIndex); + GarbageMatteActor->Destroy(); + GarbageMatteActor = nullptr; + } } GarbageMatteActor = NewActor; diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MrcLensDistortion.cpp b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MrcLensDistortion.cpp deleted file mode 100644 index d8170561ebf3..000000000000 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Private/MrcLensDistortion.cpp +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. - -#include "MrcLensDistortion.h" -#include "Engine/Texture2D.h" -#include "Math/Float16.h" - -#if WITH_OPENCV -OPENCV_INCLUDES_START -#undef check // the check macro causes problems with opencv headers -#include "opencv2/calib3d.hpp" -#include "opencv2/imgproc.hpp" -OPENCV_INCLUDES_END - -using namespace cv; - -FMrcLensDistortion::FMrcLensDistortion(const cv::Mat & DistCoeffs, const cv::Mat & CameraMatrix) -{ - // The DistCoeffs matrix is a one row matrix: - check(DistCoeffs.rows == 1); - K1 = DistCoeffs.at(0); - K2 = DistCoeffs.at(1); - P1 = DistCoeffs.at(2); - P2 = DistCoeffs.at(3); - - // The docs make it sound like third (and fourth and fifth) radial coefficients may be optional - K3 = DistCoeffs.cols >= 5 ? DistCoeffs.at(4) : .0f; - K4 = DistCoeffs.cols >= 6 ? DistCoeffs.at(5) : .0f; - K5 = DistCoeffs.cols >= 7 ? DistCoeffs.at(6) : .0f; - K6 = DistCoeffs.cols >= 8 ? DistCoeffs.at(7) : .0f; - - check(CameraMatrix.rows == 3 && CameraMatrix.cols == 3); - F.X = CameraMatrix.at(0, 0); - F.Y = CameraMatrix.at(1, 1); - C.X = CameraMatrix.at(0, 2); - C.Y = CameraMatrix.at(1, 2); -} - -Mat FMrcLensDistortion::GetDistCoeffs() -{ - Mat DistCoeffs(1, 8, CV_64F); - DistCoeffs.at(0) = K1; - DistCoeffs.at(1) = K2; - DistCoeffs.at(2) = P1; - DistCoeffs.at(3) = P2; - DistCoeffs.at(4) = K3; - DistCoeffs.at(5) = K4; - DistCoeffs.at(6) = K5; - DistCoeffs.at(7) = K6; - return DistCoeffs; -} - -Mat FMrcLensDistortion::GetCameraMatrix() -{ - Mat CameraMatrix = Mat::eye(3, 3, CV_64F); - CameraMatrix.at(0, 0) = F.X; - CameraMatrix.at(1, 1) = F.Y; - CameraMatrix.at(0, 2) = C.X; - CameraMatrix.at(1, 2) = C.Y; - return CameraMatrix; -} - - -#endif - -UTexture2D * FMrcLensDistortion::CreateUndistortUVMap(FIntPoint ImageSize, float Alpha, float & UndistortedHFOV, float & UndistortedVFOV, float & UndistortedFocalRatio) -{ -#if WITH_OPENCV - if (IsSet()) - { - - Mat Map1(ImageSize.X, ImageSize.Y, CV_32FC1); - Mat Map2(ImageSize.X, ImageSize.Y, CV_32FC1); - - // Use OpenCV to generate the map - { - Size ImgSzCV(ImageSize.X, ImageSize.Y); - - Mat Identity = Mat::eye(3, 3, CV_64F); - Mat CameraMatrix = GetCameraMatrix(); - Mat DistCoeffs = GetDistCoeffs(); - - // Calculate a new camera matrix based on the camera distortion coefficients and the passed in Alpha parameter - Mat NewCameraMatrix = getOptimalNewCameraMatrix(CameraMatrix, DistCoeffs, ImgSzCV, Alpha); - - // Create UV lookup arrays that do the undistortion - initUndistortRectifyMap(CameraMatrix, DistCoeffs, Identity, NewCameraMatrix, ImgSzCV, Map1.type(), Map1, Map2); - - // Estimate field of view of the undistorted image - double AspectRatio, FovX, FovY, FocalLength_Unused; - Point2d PrincipalPoint_Unused; - // We pass in zero aperture size as it is unknown. (It is only required for calculating focal length and the principal point) - calibrationMatrixValues(NewCameraMatrix, ImgSzCV, 0.0, 0.0, FovX, FovY, FocalLength_Unused, PrincipalPoint_Unused, AspectRatio); - UndistortedHFOV = FovX; - UndistortedVFOV = FovY; - UndistortedFocalRatio = AspectRatio; - } - - // Now convert the raw map arrays to an unreal texture - UTexture2D* Result = UTexture2D::CreateTransient( - ImageSize.X, - ImageSize.Y, - PF_G16R16F - ); - - // Lock the texture so it can be modified - FTexture2DMipMap& Mip = Result->PlatformData->Mips[0]; - uint16* MipData = static_cast(Mip.BulkData.Lock(LOCK_READ_WRITE)); - - // Generate pixel data - for (int32 Y = 0; Y < ImageSize.Y; Y++) - { - uint16* Row = &MipData[Y * ImageSize.X * 2]; - for (int32 X = 0; X < ImageSize.X; X++) - { - float UOffset = Map1.at(Y, X); - UOffset -= X; - UOffset /= ImageSize.X; - - float VOffset = Map2.at(Y, X); - VOffset -= Y; - VOffset /= ImageSize.Y; - - // red channel: - Row[X * 2 + 0] = FFloat16(UOffset).Encoded; - // green channel: - Row[X * 2 + 1] = FFloat16(VOffset).Encoded; - } - } - // Unlock the texture data - Mip.BulkData.Unlock(); - Result->UpdateResource(); - - return Result; - } - else -#endif - { - return nullptr; - } -} - diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MixedRealityCaptureComponent.h b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MixedRealityCaptureComponent.h index 088de765e843..f2e5e1294f0a 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MixedRealityCaptureComponent.h +++ b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MixedRealityCaptureComponent.h @@ -3,13 +3,14 @@ #pragma once #include "Components/SceneCaptureComponent2D.h" +#include "Delegates/Delegate.h" #include "InputCoreTypes.h" // for EControllerHand #include "Math/Color.h" // for FLinearColor -#include "MrcVideoCaptureDevice.h" -#include "Delegates/Delegate.h" -#include "Templates/SubclassOf.h" #include "MrcCalibrationData.h" -#include "MrcLensDistortion.h" +#include "MrcVideoCaptureDevice.h" +#include "OpenCVLensDistortionParameters.h" +#include "Templates/SubclassOf.h" + #include "MixedRealityCaptureComponent.generated.h" class UMediaPlayer; @@ -49,7 +50,7 @@ public: FMrcVideoCaptureFeedIndex CaptureFeedRef; UPROPERTY(EditAnywhere, BlueprintReadWrite, BlueprintSetter=SetLensDistortionParameters, Category=VideoCapture) - FMrcLensDistortion LensDistortionParameters; + FOpenCVLensDistortionParameters LensDistortionParameters; UPROPERTY(EditAnywhere, BlueprintReadOnly, Category=Tracking) FName TrackingSourceName; @@ -154,7 +155,7 @@ public: void SetCaptureDevice(const FMrcVideoCaptureFeedIndex& FeedRef); UFUNCTION(BlueprintSetter) - void SetLensDistortionParameters(const FMrcLensDistortion& ModelRef); + void SetLensDistortionParameters(const FOpenCVLensDistortionParameters& ModelRef); UFUNCTION(BlueprintGetter) int32 GetTrackingDelay() const; @@ -253,8 +254,7 @@ private: UTexture2D* DistortionDisplacementMap; float CalibratedFOV; - float LensFocalLenRatio; // Fy/Fx - float UndistortedFOV; + FOpenCVCameraViewInfo UndistortedCameraInfo; TSharedPtr ViewExtension; }; diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcCalibrationData.h b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcCalibrationData.h index 1b37a3ddaff7..39fdb5113eac 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcCalibrationData.h +++ b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcCalibrationData.h @@ -3,10 +3,11 @@ #pragma once #include "GameFramework/SaveGame.h" +#include "HeadMountedDisplayTypes.h" // for EHMDTrackingOrigin #include "Math/Color.h" // for FLinearColor #include "MrcVideoCaptureDevice.h" // for FMrcVideoCaptureFeedIndex -#include "MrcLensDistortion.h" -#include "HeadMountedDisplayTypes.h" // for EHMDTrackingOrigin +#include "OpenCVLensDistortionParameters.h" // for FOpenCVLensDistortionParameters + #include "MrcCalibrationData.generated.h" class UMaterialInstanceDynamic; @@ -20,7 +21,7 @@ struct FMrcLensCalibrationData float FOV = 90.f; UPROPERTY(BlueprintReadWrite, Category = Data) - FMrcLensDistortion DistortionParameters; + FOpenCVLensDistortionParameters DistortionParameters; }; USTRUCT(BlueprintType) diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcGarbageMatteCaptureComponent.h b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcGarbageMatteCaptureComponent.h index 657f6a14f59b..62463e0ea9e0 100644 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcGarbageMatteCaptureComponent.h +++ b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcGarbageMatteCaptureComponent.h @@ -13,7 +13,7 @@ class UMrcCalibrationData; /** * */ -UCLASS(ClassGroup = Rendering, editinlinenew, config = Engine) +UCLASS(ClassGroup = Rendering, editinlinenew, config = Engine, meta=(BlueprintSpawnableComponent)) class MIXEDREALITYCAPTUREFRAMEWORK_API UMrcGarbageMatteCaptureComponent : public USceneCaptureComponent2D { GENERATED_UCLASS_BODY() @@ -42,6 +42,9 @@ public: UFUNCTION(BlueprintNativeEvent, Category = "MixedRealityCapture|GarbageMatting") AMrcGarbageMatteActor* SpawnNewGarbageMatteActor(USceneComponent* TrackingOrigin); +private: + void CleanupSpawnedActors(); + private: UPROPERTY(Transient, Config) TSubclassOf GarbageMatteActorClass; @@ -49,6 +52,9 @@ private: UPROPERTY(Transient) AMrcGarbageMatteActor* GarbageMatteActor; + UPROPERTY(Transient) + TArray SpawnedActors; + UPROPERTY(Transient) TWeakObjectPtr TrackingOriginPtr; }; @@ -59,7 +65,7 @@ private: class UMaterial; class UStaticMesh; -UCLASS(notplaceable, Blueprintable) +UCLASS(Blueprintable) class AMrcGarbageMatteActor : public AActor { GENERATED_UCLASS_BODY() diff --git a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcLensDistortion.h b/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcLensDistortion.h deleted file mode 100644 index 0653fd882901..000000000000 --- a/Engine/Plugins/Runtime/MixedRealityCaptureFramework/Source/MixedRealityCaptureFramework/Public/MrcLensDistortion.h +++ /dev/null @@ -1,120 +0,0 @@ -// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. -#pragma once -#include "CoreMinimal.h" - -#if WITH_OPENCV -#include "OpenCVHelper.h" -OPENCV_INCLUDES_START -#undef check // the check macro causes problems with opencv headers -#include "opencv2/core/core.hpp" -#include "opencv2/imgcodecs.hpp" -OPENCV_INCLUDES_END -#endif -#include "MrcLensDistortion.generated.h" - -USTRUCT(BlueprintType) -struct MIXEDREALITYCAPTUREFRAMEWORK_API FMrcLensDistortion -{ - GENERATED_USTRUCT_BODY() - -public: - - FMrcLensDistortion() - : K1(0.f) - , K2(0.f) - , P1(0.f) - , P2(0.f) - , K3(0.f) - , K4(0.f) - , K5(0.f) - , F(FVector2D(1.f, 1.f)) - , C(FVector2D(0.5f, 0.5f)) - {} - -#if WITH_OPENCV - FMrcLensDistortion(const cv::Mat& DistCoeffs, const cv::Mat& CameraMatrix); - - cv::Mat GetDistCoeffs(); - cv::Mat GetCameraMatrix(); -#endif - - /** Compare two lens distortion models and return whether they are equal. */ - bool operator == (const FMrcLensDistortion& Other) const - { - return ( - K1 == Other.K1 && - K2 == Other.K2 && - P1 == Other.P1 && - P2 == Other.P2 && - K3 == Other.K3 && - K4 == Other.K4 && - K5 == Other.K5 && - F == Other.F && - C == Other.C); - } - - /** Compare two lens distortion models and return whether they are different. */ - bool operator != (const FMrcLensDistortion& Other) const - { - return !(*this == Other); - } - - /** Returns true if the the object contains intitialized distortion parameters */ - bool IsSet() const - { - return *this != FMrcLensDistortion(); - } - - /** Radial parameter #1. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - float K1; - - /** Radial parameter #2. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - float K2; - - /** Tangential parameter #1. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - float P1; - - /** Tangential parameter #2. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - float P2; - - /** Radial parameter #3. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - float K3; - - /** Radial parameter #4. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - float K4; - - /** Radial parameter #5. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - float K5; - - /** Radial parameter #6. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - float K6; - - - /** Camera matrix's Fx and Fy. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - FVector2D F; - - /** Camera matrix's Cx and Cy. */ - UPROPERTY(Interp, EditAnywhere, BlueprintReadWrite, Category = "MixedRealityCapture|LensCalibration") - FVector2D C; - - /** - * Creates a texture containing a UVMap in the Red and the Green channel for undistorting a camera image. - * @param ImageSize The size of the camera image to be undistorted in pixels. - * @param Alpha How much to scale the undistorted image to compensate for uneven edges. 0.0 means the image will be scaled to hide invalid pixels on the edges, 1.0 will retain all source image pixels. - * Use an intermediate value for a scaling result between the two edge cases. - * @param UndistortedHFOV the horizontal field of view of the undistorted image. - * @param UndistortedVFOV the vertical field of view of the undistorted image. - * @param UndistortedFocalRatio the focal length aspect ratio of the undistorted image. - */ - class UTexture2D* CreateUndistortUVMap(FIntPoint ImageSize, float Alpha, float& UndistortedHFOV, float& UndistortedVFOV, float& UndistortedFocalRatio); - -}; \ No newline at end of file diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/DisplayCluster.Build.cs b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/DisplayCluster.Build.cs new file mode 100644 index 000000000000..2ff24dec0a08 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/DisplayCluster.Build.cs @@ -0,0 +1,102 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +public class DisplayCluster : ModuleRules +{ + private string ModulePath + { + get + { + //return Path.GetDirectoryName(RulesCompiler.GetModuleFilename(this.GetType().Name)); + string ModuleFilename = UnrealBuildTool.RulesCompiler.GetFileNameFromType(GetType()); + string ModuleBaseDirectory = Path.GetDirectoryName(ModuleFilename); + return ModuleBaseDirectory; + } + } + + private string ThirdPartyPath + { + get + { + return Path.GetFullPath(Path.Combine(ModulePath, "../../ThirdParty/")); + } + } + + public DisplayCluster(ReadOnlyTargetRules ROTargetRules) : base(ROTargetRules) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PrivateIncludePaths.AddRange( + new string[] { + "DisplayCluster/Private", + "../../../../../Engine/Source/Runtime/Renderer/Private", + "../../../../../Engine/Source/Runtime/Windows/D3D11RHI/Private", + "../../../../../Engine/Source/Runtime/Windows/D3D11RHI/Private/Windows", + "../../../../../Engine/Source/Runtime/D3D12RHI/Private", + "../../../../../Engine/Source/Runtime/D3D12RHI/Private/Windows" + }); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore" + }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "D3D11RHI", + "D3D12RHI", + "Engine", + "HeadMountedDisplay", + "InputCore", + "Networking", + "OpenGLDrv", + "RHI", + "RenderCore", + "Slate", + "SlateCore", + "Sockets" + }); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + + PublicAdditionalLibraries.Add("opengl32.lib"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenGL"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "DX11"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "DX12"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "NVAftermath"); + + // vrpn + AddDependencyVrpn(ROTargetRules); + } + + public bool AddDependencyVrpn(ReadOnlyTargetRules ROTargetRules) + { + if ((ROTargetRules.Platform == UnrealTargetPlatform.Win64) || (ROTargetRules.Platform == UnrealTargetPlatform.Win32)) + { + string PlatformString = (ROTargetRules.Platform == UnrealTargetPlatform.Win64) ? "x64" : "x86"; + string LibrariesPath = Path.Combine(ThirdPartyPath, "VRPN", "Lib/" + PlatformString); + + //@todo: There are also debug versions: vrpnd.lib and quatd.lib + PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "vrpn.lib")); + PublicAdditionalLibraries.Add(Path.Combine(LibrariesPath, "quat.lib")); + + PublicIncludePaths.Add(Path.Combine(ThirdPartyPath, "VRPN", "Include")); + + return true; + } + + return false; + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintAPIImpl.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintAPIImpl.cpp new file mode 100644 index 000000000000..1dc0550631e6 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintAPIImpl.cpp @@ -0,0 +1,481 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterBlueprintAPIImpl.h" + +#include "IDisplayCluster.h" + +#include "Cluster/IDisplayClusterClusterManager.h" +#include "Config/IDisplayClusterConfigManager.h" +#include "Game/IDisplayClusterGameManager.h" +#include "Input/IDisplayClusterInputManager.h" +#include "Render/IDisplayClusterRenderManager.h" +#include "Render/IDisplayClusterStereoDevice.h" + + + +////////////////////////////////////////////////////////////////////////////////////////////// +// Cluster API +////////////////////////////////////////////////////////////////////////////////////////////// +bool UDisplayClusterBlueprintAPIImpl::IsMaster() +{ + IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr(); + if (Manager) + { + return Manager->IsMaster(); + } + + return false; +} + +bool UDisplayClusterBlueprintAPIImpl::IsSlave() +{ + return !IsMaster(); +} + +bool UDisplayClusterBlueprintAPIImpl::IsCluster() +{ + IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr(); + if (Manager) + { + return Manager->IsCluster(); + } + + return false; +} + +bool UDisplayClusterBlueprintAPIImpl::IsStandalone() +{ + return !IsCluster(); +} + +FString UDisplayClusterBlueprintAPIImpl::GetNodeId() +{ + IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr(); + if (Manager) + { + return Manager->GetNodeId(); + } + + return FString(); +} + +int32 UDisplayClusterBlueprintAPIImpl::GetNodesAmount() +{ + IDisplayClusterClusterManager* const Manager = IDisplayCluster::Get().GetClusterMgr(); + if (Manager) + { + return Manager->GetNodesAmount(); + } + + return 0; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// Config API +////////////////////////////////////////////////////////////////////////////////////////////// + + + +////////////////////////////////////////////////////////////////////////////////////////////// +// Game API +////////////////////////////////////////////////////////////////////////////////////////////// +// Root +ADisplayClusterPawn* UDisplayClusterBlueprintAPIImpl::GetRoot() +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetRoot(); + } + + return nullptr; +} + +// Screens +UDisplayClusterScreenComponent* UDisplayClusterBlueprintAPIImpl::GetActiveScreen() +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetActiveScreen(); + } + + return nullptr; +} + +UDisplayClusterScreenComponent* UDisplayClusterBlueprintAPIImpl::GetScreenById(const FString& id) +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetScreenById(id); + } + + return nullptr; +} + +TArray UDisplayClusterBlueprintAPIImpl::GetAllScreens() +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetAllScreens(); + } + + return TArray(); +} + +int32 UDisplayClusterBlueprintAPIImpl::GetScreensAmount() +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetScreensAmount(); + } + + return 0; +} + +// Cameras + + +// Nodes +UDisplayClusterSceneComponent* UDisplayClusterBlueprintAPIImpl::GetNodeById(const FString& id) +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetNodeById(id); + } + + return nullptr; +} + +TArray UDisplayClusterBlueprintAPIImpl::GetAllNodes() +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetAllNodes(); + } + + return TArray(); +} + +// Navigation +USceneComponent* UDisplayClusterBlueprintAPIImpl::GetTranslationDirectionComponent() +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetTranslationDirectionComponent(); + } + + return nullptr; +} + +void UDisplayClusterBlueprintAPIImpl::SetTranslationDirectionComponent(USceneComponent* const pComp) +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + Manager->SetTranslationDirectionComponent(pComp); + } +} + +void UDisplayClusterBlueprintAPIImpl::SetTranslationDirectionComponentId(const FString& id) +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + Manager->SetTranslationDirectionComponent(id); + } +} + +USceneComponent* UDisplayClusterBlueprintAPIImpl::GetRotateAroundComponent() +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + return Manager->GetRotateAroundComponent(); + } + + return nullptr; +} + +void UDisplayClusterBlueprintAPIImpl::SetRotateAroundComponent(USceneComponent* const pComp) +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + Manager->SetRotateAroundComponent(pComp); + } +} + +void UDisplayClusterBlueprintAPIImpl::SetRotateAroundComponentId(const FString& id) +{ + IDisplayClusterGameManager* const Manager = IDisplayCluster::Get().GetGameMgr(); + if (Manager) + { + Manager->SetRotateAroundComponent(id); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// Input API +////////////////////////////////////////////////////////////////////////////////////////////// +// Device information +int32 UDisplayClusterBlueprintAPIImpl::GetAxisDeviceAmount() +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + Manager->GetAxisDeviceAmount(); + } + + return 0; +} + +int32 UDisplayClusterBlueprintAPIImpl::GetButtonDeviceAmount() +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + Manager->GetButtonDeviceAmount(); + } + + return 0; +} + +int32 UDisplayClusterBlueprintAPIImpl::GetTrackerDeviceAmount() +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + Manager->GetTrackerDeviceAmount(); + } + + return 0; +} + +bool UDisplayClusterBlueprintAPIImpl::GetAxisDeviceIds(TArray& IDs) +{ + TArray result; + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + return Manager->GetAxisDeviceIds(IDs); + } + + return false; +} + +bool UDisplayClusterBlueprintAPIImpl::GetButtonDeviceIds(TArray& IDs) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + return Manager->GetButtonDeviceIds(IDs); + } + + return false; +} + +bool UDisplayClusterBlueprintAPIImpl::GetTrackerDeviceIds(TArray& IDs) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + return Manager->GetTrackerDeviceIds(IDs); + } + + return false; +} + +// Buttons +void UDisplayClusterBlueprintAPIImpl::GetButtonState(const FString& DeviceId, uint8 DeviceChannel, bool& CurState, bool& IsChannelAvailable) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + IsChannelAvailable = Manager->GetButtonState(DeviceId, DeviceChannel, CurState); + } +} + +void UDisplayClusterBlueprintAPIImpl::IsButtonPressed(const FString& DeviceId, uint8 DeviceChannel, bool& CurPressed, bool& IsChannelAvailable) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + IsChannelAvailable = Manager->IsButtonPressed(DeviceId, DeviceChannel, CurPressed); + } +} + +void UDisplayClusterBlueprintAPIImpl::IsButtonReleased(const FString& DeviceId, uint8 DeviceChannel, bool& CurReleased, bool& IsChannelAvailable) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + IsChannelAvailable = Manager->IsButtonReleased(DeviceId, DeviceChannel, CurReleased); + } +} + +void UDisplayClusterBlueprintAPIImpl::WasButtonPressed(const FString& DeviceId, uint8 DeviceChannel, bool& WasPressed, bool& IsChannelAvailable) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + IsChannelAvailable = Manager->WasButtonPressed(DeviceId, DeviceChannel, WasPressed); + } +} + +void UDisplayClusterBlueprintAPIImpl::WasButtonReleased(const FString& DeviceId, uint8 DeviceChannel, bool& WasReleased, bool& IsChannelAvailable) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + IsChannelAvailable = Manager->WasButtonReleased(DeviceId, DeviceChannel, WasReleased); + } +} + +// Axes +void UDisplayClusterBlueprintAPIImpl::GetAxis(const FString& DeviceId, uint8 DeviceChannel, float& Value, bool& IsChannelAvailable) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + IsChannelAvailable = Manager->GetAxis(DeviceId, DeviceChannel, Value); + } +} + +// Trackers +void UDisplayClusterBlueprintAPIImpl::GetTrackerLocation(const FString& DeviceId, uint8 DeviceChannel, FVector& Location, bool& IsChannelAvailable) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + IsChannelAvailable = Manager->GetTrackerLocation(DeviceId, DeviceChannel, Location); + } +} + +void UDisplayClusterBlueprintAPIImpl::GetTrackerQuat(const FString& DeviceId, uint8 DeviceChannel, FQuat& Rotation, bool& IsChannelAvailable) +{ + IDisplayClusterInputManager* const Manager = IDisplayCluster::Get().GetInputMgr(); + if (Manager) + { + IsChannelAvailable = Manager->GetTrackerQuat(DeviceId, DeviceChannel, Rotation); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// Render API +////////////////////////////////////////////////////////////////////////////////////////////// +void UDisplayClusterBlueprintAPIImpl::SetInterpupillaryDistance(float dist) +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + return Manager->GetStereoDevice()->SetInterpupillaryDistance(dist); + } + + return; +} + +float UDisplayClusterBlueprintAPIImpl::GetInterpupillaryDistance() +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + return Manager->GetStereoDevice()->GetInterpupillaryDistance(); + } + + return 0.f; +} + +void UDisplayClusterBlueprintAPIImpl::SetEyesSwap(bool swap) +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + return Manager->GetStereoDevice()->SetEyesSwap(swap); + } + + return; +} + +bool UDisplayClusterBlueprintAPIImpl::GetEyesSwap() +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + return Manager->GetStereoDevice()->GetEyesSwap(); + } + + return false; +} + +bool UDisplayClusterBlueprintAPIImpl::ToggleEyesSwap() +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + return Manager->GetStereoDevice()->ToggleEyesSwap(); + } + + return false; +} + +void UDisplayClusterBlueprintAPIImpl::SetOutputFlip(bool flipH, bool flipV) +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + return Manager->GetStereoDevice()->SetOutputFlip(flipH, flipV); + } + + return; +} + +void UDisplayClusterBlueprintAPIImpl::GetOutputFlip(bool& flipH, bool& flipV) +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + return Manager->GetStereoDevice()->GetOutputFlip(flipH, flipV); + } + + return; +} + +void UDisplayClusterBlueprintAPIImpl::GetCullingDistance(float& NearClipPlane, float& FarClipPlane) +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + IDisplayClusterStereoDevice* pDev = Manager->GetStereoDevice(); + if (pDev) + { + return pDev->GetCullingDistance(NearClipPlane, FarClipPlane); + } + } + + return; +} + +void UDisplayClusterBlueprintAPIImpl::SetCullingDistance(float NearClipPlane, float FarClipPlane) +{ + IDisplayClusterRenderManager* const Manager = IDisplayCluster::Get().GetRenderMgr(); + if (Manager) + { + IDisplayClusterStereoDevice* pDev = Manager->GetStereoDevice(); + if (pDev) + { + return pDev->SetCullingDistance(NearClipPlane, FarClipPlane); + } + } + + return; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintAPIImpl.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintAPIImpl.h new file mode 100644 index 000000000000..89d955c9ed02 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintAPIImpl.h @@ -0,0 +1,185 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprints/IDisplayClusterBlueprintAPI.h" +#include "DisplayClusterBlueprintAPIImpl.generated.h" + + +/** + * Blueprint API interface implementation + */ +UCLASS() +class DISPLAYCLUSTER_API UDisplayClusterBlueprintAPIImpl + : public UObject + , public IDisplayClusterBlueprintAPI +{ + GENERATED_BODY() + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Cluster API + ////////////////////////////////////////////////////////////////////////////////////////////// + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is master node"), Category = "DisplayCluster|Cluster") + virtual bool IsMaster() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is slave node"), Category = "DisplayCluster|Cluster") + virtual bool IsSlave() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is cluster mode"), Category = "DisplayCluster|Cluster") + virtual bool IsCluster() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is standalone mode"), Category = "DisplayCluster|Cluster") + virtual bool IsStandalone() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get node ID"), Category = "DisplayCluster|Cluster") + virtual FString GetNodeId() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get nodes amount"), Category = "DisplayCluster|Cluster") + virtual int32 GetNodesAmount() override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Config API + ////////////////////////////////////////////////////////////////////////////////////////////// + + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Game API + ////////////////////////////////////////////////////////////////////////////////////////////// + // Root + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get root"), Category = "DisplayCluster|Game") + virtual ADisplayClusterPawn* GetRoot() override; + + // Screens + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get active screen"), Category = "DisplayCluster|Game") + virtual UDisplayClusterScreenComponent* GetActiveScreen() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get screen by ID"), Category = "DisplayCluster|Game") + virtual UDisplayClusterScreenComponent* GetScreenById(const FString& id) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get all screens"), Category = "DisplayCluster|Game") + virtual TArray GetAllScreens() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get amount of screens"), Category = "DisplayCluster|Game") + virtual int32 GetScreensAmount() override; + + // Cameras + /* + virtual UDisplayClusterCameraComponent* GetActiveCamera() const override; + virtual UDisplayClusterCameraComponent* GetCameraById(const FString& id) const override; + virtual TArray GetAllCameras() const override; + virtual int32 GetCamerasAmount() const override; + virtual void SetActiveCamera(int32 idx) override; + virtual void SetActiveCamera(const FString& id) override; + */ + + // Nodes + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get node by ID"), Category = "DisplayCluster|Game") + virtual UDisplayClusterSceneComponent* GetNodeById(const FString& id) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get all nodes"), Category = "DisplayCluster|Game") + virtual TArray GetAllNodes() override; + + // Navigation + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get translation direction component"), Category = "DisplayCluster|Game") + virtual USceneComponent* GetTranslationDirectionComponent() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set translation direction component"), Category = "DisplayCluster|Game") + virtual void SetTranslationDirectionComponent(USceneComponent* pComp) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set translation direction component by ID"), Category = "DisplayCluster|Game") + virtual void SetTranslationDirectionComponentId(const FString& id) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get rotate around component"), Category = "DisplayCluster|Game") + virtual USceneComponent* GetRotateAroundComponent() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set rotate around component"), Category = "DisplayCluster|Game") + virtual void SetRotateAroundComponent(USceneComponent* pComp) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set rotate around component by ID"), Category = "DisplayCluster|Game") + virtual void SetRotateAroundComponentId(const FString& id) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Input API + ////////////////////////////////////////////////////////////////////////////////////////////// + // Device information + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get amount of VRPN axis devices"), Category = "DisplayCluster|Input") + virtual int32 GetAxisDeviceAmount() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get amount of VRPN button devices"), Category = "DisplayCluster|Input") + virtual int32 GetButtonDeviceAmount() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get amount of VRPN tracker devices"), Category = "DisplayCluster|Input") + virtual int32 GetTrackerDeviceAmount() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get IDs of VRPN axis devices"), Category = "DisplayCluster|Input") + virtual bool GetAxisDeviceIds(TArray& IDs) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get IDs of VRPN button devices"), Category = "DisplayCluster|Input") + virtual bool GetButtonDeviceIds(TArray& IDs) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get IDs of VRPN tracker devices"), Category = "DisplayCluster|Input") + virtual bool GetTrackerDeviceIds(TArray& IDs) override; + + // Buttons + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get VRPN button state"), Category = "DisplayCluster|Input") + virtual void GetButtonState(const FString& DeviceId, uint8 DeviceChannel, bool& CurState, bool& IsChannelAvailable) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is VRPN button pressed"), Category = "DisplayCluster|Input") + virtual void IsButtonPressed(const FString& DeviceId, uint8 DeviceChannel, bool& CurPressed, bool& IsChannelAvailable) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is VRPN button released"), Category = "DisplayCluster|Input") + virtual void IsButtonReleased(const FString& DeviceId, uint8 DeviceChannel, bool& CurReleased, bool& IsChannelAvailable) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Was VRPN button pressed"), Category = "DisplayCluster|Input") + virtual void WasButtonPressed(const FString& DeviceId, uint8 DeviceChannel, bool& WasPressed, bool& IsChannelAvailable) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Was VRPN button released"), Category = "DisplayCluster|Input") + virtual void WasButtonReleased(const FString& DeviceId, uint8 DeviceChannel, bool& WasReleased, bool& IsChannelAvailable) override; + + // Axes + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get VRPN axis value"), Category = "DisplayCluster|Input") + virtual void GetAxis(const FString& DeviceId, uint8 DeviceChannel, float& Value, bool& IsAvailable) override; + + // Trackers + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get VRPN tracker location"), Category = "DisplayCluster|Input") + virtual void GetTrackerLocation(const FString& DeviceId, uint8 DeviceChannel, FVector& Location, bool& IsChannelAvailable) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get VRPN tracker rotation (as quaternion)"), Category = "DisplayCluster|Input") + virtual void GetTrackerQuat(const FString& DeviceId, uint8 DeviceChannel, FQuat& Rotation, bool& IsChannelAvailable) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Render API + ////////////////////////////////////////////////////////////////////////////////////////////// + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set interpuppillary distance"), Category = "DisplayCluster|Render") + virtual void SetInterpupillaryDistance(float dist) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get interpuppillary distance"), Category = "DisplayCluster|Render") + virtual float GetInterpupillaryDistance() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set eye swap"), Category = "DisplayCluster|Render") + virtual void SetEyesSwap(bool swap) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get eye swap"), Category = "DisplayCluster|Render") + virtual bool GetEyesSwap() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Toggle eye swap"), Category = "DisplayCluster|Render") + virtual bool ToggleEyesSwap() override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set output flip"), Category = "DisplayCluster|Render") + virtual void SetOutputFlip(bool flipH, bool flipV) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get output flip"), Category = "DisplayCluster|Render") + virtual void GetOutputFlip(bool& flipH, bool& flipV) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get near and far clipping distance"), Category = "DisplayCluster|Render") + virtual void GetCullingDistance(float& NearClipPlane, float& FarClipPlane) override; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set near and far clipping distance"), Category = "DisplayCluster|Render") + virtual void SetCullingDistance(float NearClipPlane, float FarClipPlane) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintLib.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintLib.cpp new file mode 100644 index 000000000000..d86b244d212a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Blueprints/DisplayClusterBlueprintLib.cpp @@ -0,0 +1,17 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Blueprints/DisplayClusterBlueprintLib.h" +#include "DisplayClusterBlueprintAPIImpl.h" + + +UDisplayClusterBlueprintLib::UDisplayClusterBlueprintLib(class FObjectInitializer const & ObjectInitializer) : + Super(ObjectInitializer) +{ + +} + +void UDisplayClusterBlueprintLib::GetAPI(TScriptInterface& OutAPI) +{ + static TUniquePtr Obj(NewObject()); + OutAPI = Obj.Get(); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlBase.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlBase.cpp new file mode 100644 index 000000000000..ad52bb0fd124 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlBase.cpp @@ -0,0 +1,65 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterClusterNodeCtrlBase.h" +#include "DisplayClusterGlobals.h" + +#include "Config/DisplayClusterConfigTypes.h" +#include "Misc/DisplayClusterLog.h" + +#include "IPDisplayCluster.h" +#include "Config/IPDisplayClusterConfigManager.h" +#include "Render/IPDisplayClusterRenderManager.h" +#include "Render/IDisplayClusterStereoDevice.h" + + + +FDisplayClusterClusterNodeCtrlBase::FDisplayClusterClusterNodeCtrlBase(const FString& ctrlName, const FString& nodeName) : + FDisplayClusterNodeCtrlBase(ctrlName, nodeName) +{ + +} + +FDisplayClusterClusterNodeCtrlBase::~FDisplayClusterClusterNodeCtrlBase() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterNodeController +////////////////////////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterNodeCtrlBase +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterClusterNodeCtrlBase::InitializeStereo() +{ + FDisplayClusterConfigViewport ViewportCfg; + if (!GDisplayCluster->GetPrivateConfigMgr()->GetLocalViewport(ViewportCfg)) + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("Viewport config not found")); + return false; + } + + IDisplayClusterStereoDevice* const StereoDevice = GDisplayCluster->GetPrivateRenderMgr()->GetStereoDevice(); + if (StereoDevice) + { + + FDisplayClusterConfigStereo StereoCfg = GDisplayCluster->GetPrivateConfigMgr()->GetConfigStereo(); + FDisplayClusterConfigGeneral GeneralCfg = GDisplayCluster->GetPrivateConfigMgr()->GetConfigGeneral(); + //FDisplayClusterConfigRender RenderCfg = GDisplayCluster->GetPrivateConfigMgr()->GetConfigRender(); + + // Configure the device + StereoDevice->SetViewportArea(ViewportCfg.Loc, ViewportCfg.Size); + StereoDevice->SetEyesSwap(StereoCfg.EyeSwap); + StereoDevice->SetInterpupillaryDistance(StereoCfg.EyeDist); + StereoDevice->SetOutputFlip(ViewportCfg.FlipHorizontal, ViewportCfg.FlipVertical); + StereoDevice->SetSwapSyncPolicy((EDisplayClusterSwapSyncPolicy)GeneralCfg.SwapSyncPolicy); + } + else + { + UE_LOG(LogDisplayClusterRender, Warning, TEXT("Stereo device not found. Stereo initialization skipped.")); + } + + return FDisplayClusterNodeCtrlBase::InitializeStereo(); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlBase.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlBase.h new file mode 100644 index 000000000000..5f180908bc90 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlBase.h @@ -0,0 +1,32 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterNodeCtrlBase.h" + + +/** + * Abstract cluster node controller (cluster mode). + */ +class FDisplayClusterClusterNodeCtrlBase + : public FDisplayClusterNodeCtrlBase +{ +public: + FDisplayClusterClusterNodeCtrlBase(const FString& ctrlName, const FString& nodeName); + virtual ~FDisplayClusterClusterNodeCtrlBase() = 0; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterNodeController + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool IsStandalone() const override final + { return false; } + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // FDisplayClusterNodeCtrlBase + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool InitializeStereo() override; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.cpp new file mode 100644 index 000000000000..5eca40b98041 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.cpp @@ -0,0 +1,148 @@ + +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterClusterNodeCtrlMaster.h" + +#include "Config/IPDisplayClusterConfigManager.h" +#include "Network/Service/ClusterSync/DisplayClusterClusterSyncService.h" +#include "Network/Service/SwapSync/DisplayClusterSwapSyncService.h" + +#include "Misc/DisplayClusterLog.h" +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +FDisplayClusterClusterNodeCtrlMaster::FDisplayClusterClusterNodeCtrlMaster(const FString& ctrlName, const FString& nodeName) : + FDisplayClusterClusterNodeCtrlSlave(ctrlName, nodeName) +{ +} + +FDisplayClusterClusterNodeCtrlMaster::~FDisplayClusterClusterNodeCtrlMaster() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterNodeController +////////////////////////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterNodeController +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterClusterNodeCtrlMaster::GetSyncData(FDisplayClusterMessage::DataType& data) +{ + // Override slave implementation with empty one. + // There is no need to sync on master side since it have source data being synced. +} + +void FDisplayClusterClusterNodeCtrlMaster::GetInputData(FDisplayClusterMessage::DataType& data) +{ + // Override slave implementation with empty one. + // There is no need to sync on master side since it have source data being synced. +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterNodeCtrlBase +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterClusterNodeCtrlMaster::InitializeServers() +{ + if (!FDisplayClusterClusterNodeCtrlSlave::InitializeServers()) + { + return false; + } + + UE_LOG(LogDisplayClusterCluster, Log, TEXT("%s - initializing master servers..."), *GetControllerName()); + + // Get config data + FDisplayClusterConfigClusterNode masterCfg; + if (GDisplayCluster->GetPrivateConfigMgr()->GetMasterClusterNode(masterCfg) == false) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("No master node configuration data found")); + return false; + } + + // Instantiate node servers + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Servers: addr %s, port_cs %d, port_ss %d"), *masterCfg.Addr, masterCfg.Port_CS, masterCfg.Port_SS); + ClusterSyncServer.Reset(new FDisplayClusterClusterSyncService(masterCfg.Addr, masterCfg.Port_CS)); + SwapSyncServer.Reset(new FDisplayClusterSwapSyncService(masterCfg.Addr, masterCfg.Port_SS)); + + return ClusterSyncServer.IsValid() && SwapSyncServer.IsValid(); +} + +bool FDisplayClusterClusterNodeCtrlMaster::StartServers() +{ + if (!FDisplayClusterClusterNodeCtrlSlave::StartServers()) + { + return false; + } + + UE_LOG(LogDisplayClusterCluster, Log, TEXT("%s - starting master servers..."), *GetControllerName()); + + // CS server start + if (ClusterSyncServer->Start()) + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("%s started"), *ClusterSyncServer->GetName()); + } + else + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("%s failed to start"), *ClusterSyncServer->GetName()); + } + + // SS server start + if (SwapSyncServer->Start()) + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("%s started"), *SwapSyncServer->GetName()); + } + else + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("%s failed to start"), *SwapSyncServer->GetName()); + } + + // Start the servers + return ClusterSyncServer->IsRunning() && SwapSyncServer->IsRunning(); +} + +void FDisplayClusterClusterNodeCtrlMaster::StopServers() +{ + FDisplayClusterClusterNodeCtrlSlave::StopServers(); + + ClusterSyncServer->Shutdown(); + SwapSyncServer->Shutdown(); +} + +bool FDisplayClusterClusterNodeCtrlMaster::InitializeClients() +{ + if (!FDisplayClusterClusterNodeCtrlSlave::InitializeClients()) + { + return false; + } + + // Master clients initialization + // ... + + return true; +} + +bool FDisplayClusterClusterNodeCtrlMaster::StartClients() +{ + if (!FDisplayClusterClusterNodeCtrlSlave::StartClients()) + { + return false; + } + + // Master clients start + // ... + + return true; +} + +void FDisplayClusterClusterNodeCtrlMaster::StopClients() +{ + FDisplayClusterClusterNodeCtrlSlave::StopClients(); + + // Master clients stop + // ... +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.h new file mode 100644 index 000000000000..41539d0162b8 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.h @@ -0,0 +1,54 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterClusterNodeCtrlSlave.h" + +#include "Network/DisplayClusterMessage.h" + +class FDisplayClusterClusterSyncService; +class FDisplayClusterSwapSyncService; + + +/** + * Master node controller implementation (cluster mode). Manages servers on master side. + */ +class FDisplayClusterClusterNodeCtrlMaster + : public FDisplayClusterClusterNodeCtrlSlave +{ +public: + FDisplayClusterClusterNodeCtrlMaster(const FString& ctrlName, const FString& nodeName); + virtual ~FDisplayClusterClusterNodeCtrlMaster(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterNodeController + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool IsSlave() const override final + { return false; } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterNodeController + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void GetSyncData(FDisplayClusterMessage::DataType& data) override; + virtual void GetInputData(FDisplayClusterMessage::DataType& data) override; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // FDisplayClusterNodeCtrlBase + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool InitializeServers() override; + virtual bool StartServers() override; + virtual void StopServers() override; + + virtual bool InitializeClients() override; + virtual bool StartClients() override; + virtual void StopClients() override; + +private: + // Node servers + TUniquePtr ClusterSyncServer; + TUniquePtr SwapSyncServer; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.cpp new file mode 100644 index 000000000000..1b748a4f8b28 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.cpp @@ -0,0 +1,180 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterClusterNodeCtrlSlave.h" + +#include "Config/IPDisplayClusterConfigManager.h" +#include "Misc/DisplayClusterLog.h" +#include "Network/Service/ClusterSync/DisplayClusterClusterSyncClient.h" +#include "Network/Service/SwapSync/DisplayClusterSwapSyncClient.h" + +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +FDisplayClusterClusterNodeCtrlSlave::FDisplayClusterClusterNodeCtrlSlave(const FString& ctrlName, const FString& nodeName) : + FDisplayClusterClusterNodeCtrlBase(ctrlName, nodeName) +{ +} + +FDisplayClusterClusterNodeCtrlSlave::~FDisplayClusterClusterNodeCtrlSlave() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterNodeController +////////////////////////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterClusterSyncProtocol +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterClusterNodeCtrlSlave::WaitForGameStart() +{ + ClusterSyncClient->WaitForGameStart(); +} + +void FDisplayClusterClusterNodeCtrlSlave::WaitForFrameStart() +{ + ClusterSyncClient->WaitForFrameStart(); +} + +void FDisplayClusterClusterNodeCtrlSlave::WaitForFrameEnd() +{ + ClusterSyncClient->WaitForFrameEnd(); +} + +void FDisplayClusterClusterNodeCtrlSlave::WaitForTickEnd() +{ + ClusterSyncClient->WaitForTickEnd(); +} + +void FDisplayClusterClusterNodeCtrlSlave::GetDeltaTime(float& deltaTime) +{ + ClusterSyncClient->GetDeltaTime(deltaTime); +} + +void FDisplayClusterClusterNodeCtrlSlave::GetSyncData(FDisplayClusterMessage::DataType& data) +{ + ClusterSyncClient->GetSyncData(data); +} + +void FDisplayClusterClusterNodeCtrlSlave::GetInputData(FDisplayClusterMessage::DataType& data) +{ + ClusterSyncClient->GetInputData(data); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterSwapSyncProtocol +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterClusterNodeCtrlSlave::WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) +{ + check(SwapSyncClient.IsValid()); + SwapSyncClient->WaitForSwapSync(pThreadWaitTime, pBarrierWaitTime); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterNodeCtrlBase +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterClusterNodeCtrlSlave::InitializeServers() +{ + if (!FDisplayClusterClusterNodeCtrlBase::InitializeServers()) + { + return false; + } + + // Slave servers initialization + // ... + + return true; +} + +bool FDisplayClusterClusterNodeCtrlSlave::StartServers() +{ + if (!FDisplayClusterClusterNodeCtrlBase::StartServers()) + { + return false; + } + + // Slave servers start + // ... + + return true; +} + +void FDisplayClusterClusterNodeCtrlSlave::StopServers() +{ + FDisplayClusterClusterNodeCtrlBase::StopServers(); + + // Slave servers stop + // ... +} + +bool FDisplayClusterClusterNodeCtrlSlave::InitializeClients() +{ + if (!FDisplayClusterClusterNodeCtrlBase::InitializeClients()) + { + return false; + } + + UE_LOG(LogDisplayClusterCluster, Log, TEXT("%s - initializing slave clients..."), *GetControllerName()); + + // Instantiate local clients + ClusterSyncClient.Reset(new FDisplayClusterClusterSyncClient); + SwapSyncClient.Reset(new FDisplayClusterSwapSyncClient); + + return ClusterSyncClient.IsValid() && SwapSyncClient.IsValid(); +} + +bool FDisplayClusterClusterNodeCtrlSlave::StartClients() +{ + if (!FDisplayClusterClusterNodeCtrlBase::StartClients()) + { + return false; + } + + UE_LOG(LogDisplayClusterCluster, Log, TEXT("%s - initializing slave clients..."), *GetControllerName()); + + // Master config + FDisplayClusterConfigClusterNode MasterCfg; + if (GDisplayCluster->GetPrivateConfigMgr()->GetMasterClusterNode(MasterCfg) == false) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("No master node configuration data found")); + return false; + } + + // CS client + if (ClusterSyncClient->Connect(MasterCfg.Addr, MasterCfg.Port_CS)) + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("%s connected to the server %s:%d"), *ClusterSyncClient->GetName(), *MasterCfg.Addr, MasterCfg.Port_CS); + } + else + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("%s couldn't connect to the server %s:%d"), *ClusterSyncClient->GetName(), *MasterCfg.Addr, MasterCfg.Port_CS); + // No need to wait again for next client connection + return false; + } + + // SS client + if (SwapSyncClient->Connect(MasterCfg.Addr, MasterCfg.Port_SS)) + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("%s connected to the server %s:%d"), *SwapSyncClient->GetName(), *MasterCfg.Addr, MasterCfg.Port_SS); + } + else + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("%s couldn't connect to the server %s:%d"), *SwapSyncClient->GetName(), *MasterCfg.Addr, MasterCfg.Port_SS); + return false; + } + + return ClusterSyncClient->IsConnected() && SwapSyncClient->IsConnected(); +} + +void FDisplayClusterClusterNodeCtrlSlave::StopClients() +{ + FDisplayClusterClusterNodeCtrlBase::StopClients(); + + ClusterSyncClient->Disconnect(); + SwapSyncClient->Disconnect(); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.h new file mode 100644 index 000000000000..9eac8983238a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.h @@ -0,0 +1,65 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterClusterNodeCtrlBase.h" +#include "Network/DisplayClusterMessage.h" + +class FDisplayClusterClusterSyncClient; +class FDisplayClusterSwapSyncClient; + + +/** + * Slave node controller implementation (cluster mode). . Manages clients on client side. + */ +class FDisplayClusterClusterNodeCtrlSlave + : public FDisplayClusterClusterNodeCtrlBase +{ +public: + FDisplayClusterClusterNodeCtrlSlave(const FString& ctrlName, const FString& nodeName); + virtual ~FDisplayClusterClusterNodeCtrlSlave(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterNodeController + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool IsSlave() const override + { return true; } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterClusterSyncProtocol + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void WaitForGameStart() override final; + virtual void WaitForFrameStart() override final; + virtual void WaitForFrameEnd() override final; + virtual void WaitForTickEnd() override final; + virtual void GetDeltaTime(float& deltaTime) override final; + virtual void GetSyncData(FDisplayClusterMessage::DataType& data) override; + virtual void GetInputData(FDisplayClusterMessage::DataType& data) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterSwapSyncProtocol + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) override final; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // FDisplayClusterNodeCtrlBase + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool InitializeServers() override; + virtual bool StartServers() override; + virtual void StopServers() override; + + virtual bool InitializeClients() override; + virtual bool StartClients() override; + virtual void StopClients() override; + +private: + // Cluster node clients + TUniquePtr ClusterSyncClient; + TUniquePtr SwapSyncClient; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlBase.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlBase.cpp new file mode 100644 index 000000000000..aebcb96bdeb2 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlBase.cpp @@ -0,0 +1,56 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterNodeCtrlBase.h" +#include "Misc/DisplayClusterLog.h" + + +FDisplayClusterNodeCtrlBase::FDisplayClusterNodeCtrlBase(const FString& ctrlName, const FString& nodeName) : + ControllerName(ctrlName), + NodeName(nodeName) +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterNodeController +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterNodeCtrlBase::Initialize() +{ + if (!InitializeStereo()) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Stereo initialization failed")); + return false; + } + + if (!InitializeServers()) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Servers initialization failed")); + return false; + } + + if (!InitializeClients()) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Clients initialization failed")); + return false; + } + + if (!StartServers()) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("An error occurred during servers start")); + return false; + } + + if (!StartClients()) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("An error occurred during clients start")); + return false; + } + + return true; +} + +void FDisplayClusterNodeCtrlBase::Release() +{ + StopServers(); + StopClients(); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlBase.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlBase.h new file mode 100644 index 000000000000..8e885f17bb64 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlBase.h @@ -0,0 +1,71 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IPDisplayClusterNodeController.h" + +class FDisplayClusterClusterManager; + + +/** + * Abstract node controller + */ +class FDisplayClusterNodeCtrlBase + : public IPDisplayClusterNodeController +{ + // This is needed to perform initialization from outside of constructor (polymorphic init) + friend FDisplayClusterClusterManager; + +public: + FDisplayClusterNodeCtrlBase(const FString& ctrlName, const FString& nodeName); + + virtual ~FDisplayClusterNodeCtrlBase() = 0 + { } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterNodeController + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Initialize() override final; + virtual void Release() override final; + + virtual bool IsMaster() const override final + { return !IsSlave(); } + + virtual bool IsCluster() const override final + { return !IsStandalone(); } + + virtual FString GetNodeId() const override final + { return NodeName; } + + virtual FString GetControllerName() const override final + { return ControllerName; } + +protected: + virtual bool InitializeStereo() + { return true; } + + virtual bool InitializeServers() + { return true; } + + virtual bool StartServers() + { return true; } + + virtual void StopServers() + { return; } + + virtual bool InitializeClients() + { return true; } + + virtual bool StartClients() + { return true; } + + virtual void StopClients() + { return; } + +private: + const FString NodeName; + const FString ControllerName; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlStandalone.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlStandalone.cpp new file mode 100644 index 000000000000..af052db02929 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlStandalone.cpp @@ -0,0 +1,75 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterNodeCtrlStandalone.h" + +#include "Network/DisplayClusterMessage.h" +#include "Render/IDisplayClusterStereoDevice.h" + + +FDisplayClusterNodeCtrlStandalone::FDisplayClusterNodeCtrlStandalone(const FString& ctrlName, const FString& nodeName) : + FDisplayClusterNodeCtrlBase(ctrlName, nodeName) +{ +} + + +FDisplayClusterNodeCtrlStandalone::~FDisplayClusterNodeCtrlStandalone() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterClusterSyncProtocol +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterNodeCtrlStandalone::WaitForGameStart() +{ + // Nothing special to do here in standalone mode +} + +void FDisplayClusterNodeCtrlStandalone::WaitForFrameStart() +{ + // Nothing special to do here in standalone mode +} + +void FDisplayClusterNodeCtrlStandalone::WaitForFrameEnd() +{ + // Nothing special to do here in standalone mode +} + +void FDisplayClusterNodeCtrlStandalone::WaitForTickEnd() +{ + // Nothing special to do here in standalone mode +} + +void FDisplayClusterNodeCtrlStandalone::GetDeltaTime(float& deltaTime) +{ + // Nothing special to do here in standalone mode +} + +void FDisplayClusterNodeCtrlStandalone::GetSyncData(FDisplayClusterMessage::DataType& data) +{ + // Nothing special to do here in standalone mode +} + +void FDisplayClusterNodeCtrlStandalone::GetInputData(FDisplayClusterMessage::DataType& data) +{ + // Nothing special to do here in standalone mode +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterSwapSyncProtocol +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterNodeCtrlStandalone::WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) +{ + // Nothing special to do here in standalone mode +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterNodeCtrlBase +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterNodeCtrlStandalone::InitializeStereo() +{ + //@todo: initialize stereo for standalone mode + + return FDisplayClusterNodeCtrlBase::InitializeStereo(); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlStandalone.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlStandalone.h new file mode 100644 index 000000000000..89193b71898b --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/DisplayClusterNodeCtrlStandalone.h @@ -0,0 +1,53 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterNodeCtrlBase.h" + +class FDisplayClusterMessage; + + +/** + * Standalone node controller (no cluster) + */ +class FDisplayClusterNodeCtrlStandalone + : public FDisplayClusterNodeCtrlBase +{ +public: + FDisplayClusterNodeCtrlStandalone(const FString& ctrlName, const FString& nodeName); + virtual ~FDisplayClusterNodeCtrlStandalone(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterNodeController + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool IsSlave() const override final + { return false; } + + virtual bool IsStandalone() const override final + { return true; } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterClusterSyncProtocol + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void WaitForGameStart() override; + virtual void WaitForFrameStart() override; + virtual void WaitForFrameEnd() override; + virtual void WaitForTickEnd() override; + virtual void GetDeltaTime(float& deltaTime) override; + virtual void GetSyncData(FDisplayClusterMessage::DataType& data) override; + virtual void GetInputData(FDisplayClusterMessage::DataType& data) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterSwapSyncProtocol + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) override; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // FDisplayClusterNodeCtrlBase + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool InitializeStereo() override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/IPDisplayClusterNodeController.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/IPDisplayClusterNodeController.h new file mode 100644 index 000000000000..8d7d14f55f69 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/Controller/IPDisplayClusterNodeController.h @@ -0,0 +1,29 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Network/Protocol/IPDisplayClusterClusterSyncProtocol.h" +#include "Network/Protocol/IPDisplayClusterSwapSyncProtocol.h" + + +/** + * Node controller interface + */ +struct IPDisplayClusterNodeController + : public IPDisplayClusterClusterSyncProtocol + , public IPDisplayClusterSwapSyncProtocol +{ + virtual ~IPDisplayClusterNodeController() + { } + + virtual bool Initialize() = 0; + virtual void Release() = 0; + + virtual bool IsMaster() const = 0; + virtual bool IsSlave() const = 0; + virtual bool IsStandalone() const = 0; + virtual bool IsCluster() const = 0; + virtual FString GetNodeId() const = 0; + virtual FString GetControllerName() const = 0; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/DisplayClusterClusterManager.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/DisplayClusterClusterManager.cpp new file mode 100644 index 000000000000..bf0ef2efc143 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/DisplayClusterClusterManager.cpp @@ -0,0 +1,448 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterClusterManager.h" + +#include "Cluster/IDisplayClusterClusterSyncObject.h" +#include "Cluster/Controller/DisplayClusterNodeCtrlStandalone.h" +#include "Cluster/Controller/DisplayClusterClusterNodeCtrlMaster.h" +#include "Cluster/Controller/DisplayClusterClusterNodeCtrlSlave.h" + +#include "Config/IPDisplayClusterConfigManager.h" + +#include "Misc/DisplayClusterAppExit.h" +#include "Misc/DisplayClusterLog.h" +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/DisplayClusterTypesConverter.h" + +#include "Input/IPDisplayClusterInputManager.h" + +#include "DisplayClusterBuildConfig.h" +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + +#include "SocketSubsystem.h" + + +FDisplayClusterClusterManager::FDisplayClusterClusterManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + ObjectsToSync.Reserve(64); +} + +FDisplayClusterClusterManager::~FDisplayClusterClusterManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterClusterManager::Init(EDisplayClusterOperationMode OperationMode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + CurrentOperationMode = OperationMode; + + return true; +} + +void FDisplayClusterClusterManager::Release() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); +} + +bool FDisplayClusterClusterManager::StartSession(const FString& configPath, const FString& nodeId) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + ConfigPath = configPath; + ClusterNodeId = nodeId; + + if (CurrentOperationMode == EDisplayClusterOperationMode::Cluster) + { +#ifdef DISPLAY_CLUSTER_USE_AUTOMATIC_NODE_ID_RESOLVE + if (ClusterNodeId.IsEmpty()) + { + UE_LOG(LogDisplayClusterCluster, Warning, TEXT("Node name was not specified. Trying to resolve address from available interfaces...")); + + // Try to find the node ID by address (this won't work if you want to run several cluster nodes on the same address) + FString resolvedNodeId; + if (GetResolvedNodeId(resolvedNodeId)) + { + DisplayClusterHelpers::str::DustCommandLineValue(resolvedNodeId); + ClusterNodeId = resolvedNodeId; + } + else + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Unable to resolve node ID by local addresses")); + return false; + } + } +#endif + } + else if (CurrentOperationMode == EDisplayClusterOperationMode::Standalone) + { + } + else if (CurrentOperationMode == EDisplayClusterOperationMode::Editor) + { + if (ConfigPath.IsEmpty() || ClusterNodeId.IsEmpty()) + { + UE_LOG(LogDisplayClusterCluster, Warning, TEXT("Wrong config path and/or node ID. Using default standalone config.")); + +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + ConfigPath = FString(DisplayClusterStrings::misc::DbgStubConfig); + ClusterNodeId = FString(DisplayClusterStrings::misc::DbgStubNodeId); +#endif + } + } + else if (CurrentOperationMode == EDisplayClusterOperationMode::Disabled) + { + return true; + } + else + { + UE_LOG(LogDisplayClusterCluster, Warning, TEXT("Unknown operation mode")); + return false; + } + + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Node ID: %s"), *ClusterNodeId); + + // Node name must be specified in cluster mode + if (ClusterNodeId.IsEmpty()) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Node name was not specified")); + return false; + } + + // Save nodes amount + NodesAmount = GDisplayCluster->GetPrivateConfigMgr()->GetClusterNodesAmount(); + + // Instantiate node controller + Controller = CreateController(); + + if (!Controller) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Couldn't create a controller.")); + return false; + } + + // Initialize the controller + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Initializing the controller...")); + if (!Controller->Initialize()) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Couldn't initialize a controller.")); + Controller.Reset(); + return false; + } + + return true; +} + +void FDisplayClusterClusterManager::EndSession() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + { + FScopeLock lock(&InternalsSyncScope); + if (Controller) + { + Controller->Release(); + Controller.Reset(); + } + } +} + +bool FDisplayClusterClusterManager::StartScene(UWorld* pWorld) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + check(pWorld); + CurrentWorld = pWorld; + + return true; +} + +void FDisplayClusterClusterManager::EndScene() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + { + FScopeLock lock(&ObjectsToSyncCritSec); + ObjectsToSync.Reset(); + } +} + +void FDisplayClusterClusterManager::PreTick(float DeltaSeconds) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + // Clear cached data from previous game frame + { + FScopeLock lock(&ObjectsToSyncCritSec); + SyncObjectsCache.Empty(SyncObjectsCache.Num() | 0x07); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +IPDisplayClusterNodeController* FDisplayClusterClusterManager::GetController() const +{ + FScopeLock lock(&InternalsSyncScope); + return Controller ? Controller.Get() : nullptr; +} + +bool FDisplayClusterClusterManager::IsMaster() const +{ + return Controller ? Controller->IsMaster() : false; +} + +bool FDisplayClusterClusterManager::IsSlave() const +{ + return Controller ? Controller->IsSlave() : false; +} + +bool FDisplayClusterClusterManager::IsStandalone() const +{ + return Controller ? Controller->IsStandalone() : false; +} + +bool FDisplayClusterClusterManager::IsCluster() const +{ + return Controller ? Controller->IsCluster() : false; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterClusterManager::RegisterSyncObject(IDisplayClusterClusterSyncObject* pSyncObj) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + { + FScopeLock lock(&ObjectsToSyncCritSec); + ObjectsToSync.Add(pSyncObj); + } + + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Registered sync object: %s"), *pSyncObj->GetSyncId()); +} + +void FDisplayClusterClusterManager::UnregisterSyncObject(IDisplayClusterClusterSyncObject* pSyncObj) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + { + FScopeLock lock(&ObjectsToSyncCritSec); + ObjectsToSync.Remove(pSyncObj); + } + + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Unregistered sync object: %s"), *pSyncObj->GetSyncId()); +} + +void FDisplayClusterClusterManager::ExportSyncData(FDisplayClusterMessage::DataType& data) const +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + { + FScopeLock lock(&ObjectsToSyncCritSec); + + // Cache the data for current frame. + // There is on check for ObjectsToSync emptiness because we always have at least one + // shared transform which is AFDisplayClusterPawn. + if (SyncObjectsCache.Num() == 0) + { + for (auto obj : ObjectsToSync) + { + if (obj->IsDirty()) + { + UE_LOG(LogDisplayClusterCluster, Verbose, TEXT("Adding object to sync: %s"), *obj->GetSyncId()); + SyncObjectsCache.Add(obj->GetSyncId(), obj->SerializeToString()); + obj->ClearDirty(); + } + } + } + } + + data = SyncObjectsCache; +} + +void FDisplayClusterClusterManager::ImportSyncData(const FDisplayClusterMessage::DataType& data) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + if (data.Num() > 0) + { + for (auto it = data.CreateConstIterator(); it; ++it) + { + UE_LOG(LogDisplayClusterCluster, Verbose, TEXT("sync-data: %s=%s"), *it->Key, *it->Value); + } + + for (auto obj : ObjectsToSync) + { + const FString syncId = obj->GetSyncId(); + if (!data.Contains(syncId)) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("%s not found in sync data"), *syncId); + continue; + } + + UE_LOG(LogDisplayClusterCluster, Verbose, TEXT("Found %s in sync data. Applying..."), *syncId); + if (!obj->DeserializeFromString(data[syncId])) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Couldn't apply sync data for sync object %s"), *syncId); + } + } + } +} + +void FDisplayClusterClusterManager::SyncObjects() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + //@note: + // Don't use FScopeLock lock(&ObjectsToSyncCritSec) here because + // a) There are no race conditions at this point. We're in single-threaded mode right now (see UDisplayClusterGameEngine::Tick()) + // b) Performance + + // No need to do the sync for master + if (IsSlave()) + { + UE_LOG(LogDisplayClusterCluster, Verbose, TEXT("Downloading synchronization data (objects)...")); + TMap data; + Controller->GetSyncData(data); + UE_LOG(LogDisplayClusterCluster, Verbose, TEXT("Downloading finished. Available %d records (objects)."), data.Num()); + + // Perform data load (objects state update) + ImportSyncData(data); + } +} + +void FDisplayClusterClusterManager::SyncInput() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + // No need to do the sync for master + if (IsSlave()) + { + UE_LOG(LogDisplayClusterCluster, Verbose, TEXT("Downloading synchronization data (input)...")); + TMap data; + Controller->GetInputData(data); + UE_LOG(LogDisplayClusterCluster, Verbose, TEXT("Downloading finished. Available %d records (input)."), data.Num()); + + // Perform data load (objects state update) + GDisplayCluster->GetPrivateInputMgr()->ImportInputData(data); + } +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +FDisplayClusterClusterManager::TController FDisplayClusterClusterManager::CreateController() const +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Current operation mode: %s"), *FDisplayClusterTypesConverter::ToString(CurrentOperationMode)); + + // Instantiate appropriate controller depending on operation mode and cluster role + FDisplayClusterNodeCtrlBase* pController = nullptr; + if (CurrentOperationMode == EDisplayClusterOperationMode::Cluster) + { + FDisplayClusterConfigClusterNode nodeCfg; + if (GDisplayCluster->GetPrivateConfigMgr()->GetClusterNode(ClusterNodeId, nodeCfg) == false) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Configuration data for node %s not found"), *ClusterNodeId); + return nullptr; + } + + if (nodeCfg.IsMaster) + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Instantiating cluster master controller...")); + pController = new FDisplayClusterClusterNodeCtrlMaster(FString("[CTRL-M]"), ClusterNodeId); + } + else + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Instantiating cluster slave controller...")); + pController = new FDisplayClusterClusterNodeCtrlSlave(FString("[CTRL-S]"), ClusterNodeId); + } + } + else if (CurrentOperationMode == EDisplayClusterOperationMode::Standalone) + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Instantiating standalone controller")); + pController = new FDisplayClusterNodeCtrlStandalone(FString("[CTRL-STNDA]"), FString("standalone")); + } + else if (CurrentOperationMode == EDisplayClusterOperationMode::Editor) + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Instantiating cluster master controller...")); + //pController = new FDisplayClusterNodeCtrlStandalone(FString("[CTRL-STNDA]"), ClusterNodeId); + pController = new FDisplayClusterNodeCtrlStandalone(FString("[CTRL-STNDA]"), FString("standalone")); + } + else if (CurrentOperationMode == EDisplayClusterOperationMode::Disabled) + { + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Controller is not required")); + return nullptr; + } + else + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Unknown operation mode")); + return nullptr; + } + + // Return the controller + return TController(pController); +} + +bool FDisplayClusterClusterManager::GetResolvedNodeId(FString& id) const +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterCluster); + + TArray> addrs; + if (!ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->GetLocalAdapterAddresses(addrs)) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Couldn't get local addresses list. Cannot find node ID by its address.")); + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::KillImmediately, FString("Cluster manager init error")); + return false; + } + + if (addrs.Num() <= 0) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("No local addresses found")); + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::KillImmediately, FString("Cluster manager init error")); + return false; + } + + const TArray cnodes = GDisplayCluster->GetPrivateConfigMgr()->GetClusterNodes(); + + // Look for associated node in config + const FDisplayClusterConfigClusterNode* const pNode = cnodes.FindByPredicate([addrs](const FDisplayClusterConfigClusterNode& node) + { + for (auto addr : addrs) + { + const FIPv4Endpoint ep(addr); + const FString epaddr = ep.Address.ToString(); + UE_LOG(LogDisplayClusterCluster, Log, TEXT("Comparing addresses: %s - %s"), *epaddr, *node.Addr); + + //@note: don't add "127.0.0.1" or "localhost" here. There will be a bug. It has been proved already. + if (epaddr == node.Addr) + { + return true; + } + } + + return false; + }); + + if (!pNode) + { + UE_LOG(LogDisplayClusterCluster, Error, TEXT("Couldn't find any local address in config file")); + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::KillImmediately, FString("Cluster manager init error")); + return false; + } + + // Ok, we found the node ID by address (this won't work if you want to run several cluster nodes on the same address) + id = pNode->Id; + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/DisplayClusterClusterManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/DisplayClusterClusterManager.h new file mode 100644 index 000000000000..8b3d8f704d45 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/DisplayClusterClusterManager.h @@ -0,0 +1,102 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IPDisplayClusterClusterManager.h" +#include "Network/DisplayClusterMessage.h" + +class ADisplayClusterGameMode; +class ADisplayClusterSettings; + + +/** + * Cluster manager. Responsible for network communication and data replication. + */ +class FDisplayClusterClusterManager + : public IPDisplayClusterClusterManager +{ +public: + FDisplayClusterClusterManager(); + virtual ~FDisplayClusterClusterManager(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Init(EDisplayClusterOperationMode OperationMode) override; + virtual void Release() override; + virtual bool StartSession(const FString& configPath, const FString& nodeId) override; + virtual void EndSession() override; + virtual bool StartScene(UWorld* pWorld) override; + virtual void EndScene() override; + virtual void PreTick(float DeltaSeconds) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterClusterManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool IsMaster() const override; + virtual bool IsSlave() const override; + virtual bool IsStandalone() const override; + virtual bool IsCluster() const override; + + virtual FString GetNodeId() const override + { return ClusterNodeId; } + + virtual uint32 GetNodesAmount() const override + { return NodesAmount; } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterClusterManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual IPDisplayClusterNodeController* GetController() const override; + + virtual float GetDeltaTime() const override + { return DeltaTime; } + + virtual void SetDeltaTime(float deltaTime) override + { DeltaTime = deltaTime; } + + virtual void RegisterSyncObject(IDisplayClusterClusterSyncObject* pSyncObj) override; + virtual void UnregisterSyncObject(IDisplayClusterClusterSyncObject* pSyncObj) override; + + virtual void ExportSyncData(FDisplayClusterMessage::DataType& data) const override; + virtual void ImportSyncData(const FDisplayClusterMessage::DataType& data) override; + + virtual void SyncObjects() override; + virtual void SyncInput() override; + +private: + bool GetResolvedNodeId(FString& id) const; + + typedef TUniquePtr TController; + + // Factory method + TController CreateController() const; + +private: + // Controller implementation + TController Controller; + // Cluster/node props + uint32 NodesAmount = 0; + // Current time delta for sync + float DeltaTime = 0.f; + + // Current operation mode + EDisplayClusterOperationMode CurrentOperationMode; + // Current config path + FString ConfigPath; + // Current node ID + FString ClusterNodeId; + // Current world + UWorld* CurrentWorld; + + // Sync transforms + TSet ObjectsToSync; + mutable FDisplayClusterMessage::DataType SyncObjectsCache; + mutable FCriticalSection ObjectsToSyncCritSec; + + mutable FCriticalSection InternalsSyncScope; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/IPDisplayClusterClusterManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/IPDisplayClusterClusterManager.h new file mode 100644 index 000000000000..2aa75440ee4b --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Cluster/IPDisplayClusterClusterManager.h @@ -0,0 +1,37 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Cluster/IDisplayClusterClusterManager.h" +#include "IPDisplayClusterManager.h" + +#include "Network/DisplayClusterMessage.h" + +struct IPDisplayClusterNodeController; +struct IDisplayClusterClusterSyncObject; + + +/** + * Cluster manager private interface + */ +struct IPDisplayClusterClusterManager : + public IDisplayClusterClusterManager, + public IPDisplayClusterManager +{ + virtual ~IPDisplayClusterClusterManager() + { } + + virtual IPDisplayClusterNodeController* GetController() const = 0; + + virtual float GetDeltaTime() const = 0; + virtual void SetDeltaTime(float deltaTime) = 0; + + virtual void RegisterSyncObject (IDisplayClusterClusterSyncObject* pSyncObj) = 0; + virtual void UnregisterSyncObject(IDisplayClusterClusterSyncObject* pSyncObj) = 0; + + virtual void ExportSyncData(FDisplayClusterMessage::DataType& data) const = 0; + virtual void ImportSyncData(const FDisplayClusterMessage::DataType& data) = 0; + + virtual void SyncObjects() = 0; + virtual void SyncInput() = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Checker/DisplayClusterConfigChecker.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Checker/DisplayClusterConfigChecker.cpp new file mode 100644 index 000000000000..dd9129b474b4 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Checker/DisplayClusterConfigChecker.cpp @@ -0,0 +1,72 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterConfigChecker.h" +#include "Misc/DisplayClusterLog.h" + + +FDisplayClusterConfigChecker::FDisplayClusterConfigChecker() +{ + UE_LOG(LogDisplayClusterConfig, Verbose, TEXT("FDisplayClusterConfigManager .dtor")); +} + +FDisplayClusterConfigChecker::~FDisplayClusterConfigChecker() +{ + UE_LOG(LogDisplayClusterConfig, Verbose, TEXT("FDisplayClusterConfigManager .dtor")); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterConfigParserListener +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterConfigChecker::AddClusterNode(const FDisplayClusterConfigClusterNode& node) +{ + //UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found cluster node: id=%s, addr=%s, role=%s, port_cs=%d, port_ss=%d, port_ce=%d"), + // *node.Id, *node.Addr, node.IsMaster ? TEXT("master") : TEXT("slave"), node.Port_CS, node.Port_SS, node.Port_CE); +} + +void FDisplayClusterConfigChecker::AddScreen(const FDisplayClusterConfigScreen& screen) +{ + //UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found screen: id=%s, parent=%s, loc=%s, rot=%s, size=%s"), + // *screen.Id, *screen.ParentId, *screen.Loc.ToString(), *screen.Rot.ToString(), *screen.Size.ToString()); +} + +void FDisplayClusterConfigChecker::AddViewport(const FDisplayClusterConfigViewport& viewport) +{ + //UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found viewport: id=%s, loc=%s, size=%s"), + // *viewport.Id, *viewport.Loc.ToString(), *viewport.Size.ToString()); +} + +void FDisplayClusterConfigChecker::AddCamera(const FDisplayClusterConfigCamera& camera) +{ +} + +void FDisplayClusterConfigChecker::AddSceneNode(const FDisplayClusterConfigSceneNode& actor) +{ + //UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found scene node: id=%s, parent=%s, type=%d, loc=%s, rot=%s"), + // *actor.Id, *actor.ParentId, static_cast(actor.Type), *actor.Loc.ToString(), *actor.Rot.ToString()); +} + +void FDisplayClusterConfigChecker::AddGeneral(const FDisplayClusterConfigGeneral& general) +{ +} + +void FDisplayClusterConfigChecker::AddRender(const FDisplayClusterConfigRender& render) +{ +} + +void FDisplayClusterConfigChecker::AddStereo(const FDisplayClusterConfigStereo& stereo) +{ +} + +void FDisplayClusterConfigChecker::AddDebug(const FDisplayClusterConfigDebug& debug) +{ +} + +void FDisplayClusterConfigChecker::AddInput(const FDisplayClusterConfigInput& input) +{ +} + +void FDisplayClusterConfigChecker::AddCustom(const FDisplayClusterConfigCustom& custom) +{ +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Checker/DisplayClusterConfigChecker.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Checker/DisplayClusterConfigChecker.h new file mode 100644 index 000000000000..4b189ff70b83 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Checker/DisplayClusterConfigChecker.h @@ -0,0 +1,33 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Config/Parser/IDisplayClusterConfigParserListener.h" + + +/** + * Helper class to analyze if config data is correct + */ +class FDisplayClusterConfigChecker + : protected IDisplayClusterConfigParserListener +{ +public: + FDisplayClusterConfigChecker(); + ~FDisplayClusterConfigChecker(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterConfigParserListener + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void AddClusterNode (const FDisplayClusterConfigClusterNode& node) override; + virtual void AddScreen (const FDisplayClusterConfigScreen& screen) override; + virtual void AddViewport (const FDisplayClusterConfigViewport& viewport) override; + virtual void AddCamera (const FDisplayClusterConfigCamera& camera) override; + virtual void AddSceneNode (const FDisplayClusterConfigSceneNode& actor) override; + virtual void AddGeneral (const FDisplayClusterConfigGeneral& general) override; + virtual void AddRender (const FDisplayClusterConfigRender& render) override; + virtual void AddStereo (const FDisplayClusterConfigStereo& stereo) override; + virtual void AddDebug (const FDisplayClusterConfigDebug& debug) override; + virtual void AddInput (const FDisplayClusterConfigInput& input) override; + virtual void AddCustom (const FDisplayClusterConfigCustom& custom) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigManager.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigManager.cpp new file mode 100644 index 000000000000..47df72aa9125 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigManager.cpp @@ -0,0 +1,460 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterConfigManager.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" + +#include "Config/DisplayClusterConfigTypes.h" +#include "Config/Parser/DisplayClusterConfigParserText.h" +#include "Config/Parser/DisplayClusterConfigParserXml.h" +#include "Config/Parser/DisplayClusterConfigParserDebugAuto.h" + +#include "DisplayClusterBuildConfig.h" +#include "Misc/DisplayClusterLog.h" +#include "Misc/Paths.h" +#include "DisplayClusterGlobals.h" +#include "DisplayClusterStrings.h" +#include "IPDisplayCluster.h" + + +FDisplayClusterConfigManager::FDisplayClusterConfigManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); +} + +FDisplayClusterConfigManager::~FDisplayClusterConfigManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterConfigManager::Init(EDisplayClusterOperationMode OperationMode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + + return true; +} + +void FDisplayClusterConfigManager::Release() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); +} + +bool FDisplayClusterConfigManager::StartSession(const FString& configPath, const FString& nodeId) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + + ConfigPath = configPath; + ClusterNodeId = nodeId; + + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Starting session with config: %s"), *ConfigPath); + + // Load data + return LoadConfig(ConfigPath); +} + +void FDisplayClusterConfigManager::EndSession() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + + ResetConfigData(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterConfigManager +////////////////////////////////////////////////////////////////////////////////////////////// +// Cluster nodes +TArray FDisplayClusterConfigManager::GetClusterNodes() const +{ + return CfgClusterNodes; +} + +int32 FDisplayClusterConfigManager::GetClusterNodesAmount() const +{ + return CfgClusterNodes.Num(); +} + +bool FDisplayClusterConfigManager::GetClusterNode(int32 idx, FDisplayClusterConfigClusterNode& node) const +{ + return GetItem(CfgClusterNodes, idx, node, FString("GetNode")); +} + +bool FDisplayClusterConfigManager::GetClusterNode(const FString& id, FDisplayClusterConfigClusterNode& node) const +{ + return GetItem(CfgClusterNodes, id, node, FString("GetNode")); +} + +bool FDisplayClusterConfigManager::GetMasterClusterNode(FDisplayClusterConfigClusterNode& node) const +{ + const FDisplayClusterConfigClusterNode* const pFound = CfgClusterNodes.FindByPredicate([](const FDisplayClusterConfigClusterNode& item) + { + return item.IsMaster == true; + }); + + if (!pFound) + { + UE_LOG(LogDisplayClusterConfig, Error, TEXT("Master node configuration not found")); + return false; + } + + node = *pFound; + return true; +} + +bool FDisplayClusterConfigManager::GetLocalClusterNode(FDisplayClusterConfigClusterNode& node) const +{ + const FString nodeId = GDisplayCluster->GetPrivateClusterMgr()->GetNodeId(); + return GetItem(CfgClusterNodes, nodeId, node, FString("GetLocalNode")); +} + + +// Screens +TArray FDisplayClusterConfigManager::GetScreens() const +{ + return CfgScreens; +} + +int32 FDisplayClusterConfigManager::GetScreensAmount() const +{ + return CfgScreens.Num(); +} + +bool FDisplayClusterConfigManager::GetScreen(int32 idx, FDisplayClusterConfigScreen& screen) const +{ + return GetItem(CfgScreens, idx, screen, FString("GetScreen")); +} + +bool FDisplayClusterConfigManager::GetScreen(const FString& id, FDisplayClusterConfigScreen& screen) const +{ + return GetItem(CfgScreens, id, screen, FString("GetScreen")); +} + +bool FDisplayClusterConfigManager::GetLocalScreen(FDisplayClusterConfigScreen& screen) const +{ + FDisplayClusterConfigClusterNode localNode; + if (GetLocalClusterNode(localNode)) + { + return GetItem(CfgScreens, localNode.ScreenId, screen, FString("GetLocalScreen")); + } + + return false; +} + + +// Cameras +TArray FDisplayClusterConfigManager::GetCameras() const +{ + return CfgCameras; +} + +int32 FDisplayClusterConfigManager::GetCamerasAmount() const +{ + return CfgCameras.Num(); +} + +bool FDisplayClusterConfigManager::GetCamera(int32 idx, FDisplayClusterConfigCamera& camera) const +{ + return GetItem(CfgCameras, idx, camera, FString("GetCamera")); +} + +bool FDisplayClusterConfigManager::GetCamera(const FString& id, FDisplayClusterConfigCamera& camera) const +{ + return GetItem(CfgCameras, id, camera, FString("GetCamera")); +} + + +// Viewports +TArray FDisplayClusterConfigManager::GetViewports() const +{ + return CfgViewports; +} + +int32 FDisplayClusterConfigManager::GetViewportsAmount() const +{ + return static_cast(CfgViewports.Num()); +} + +bool FDisplayClusterConfigManager::GetViewport(int32 idx, FDisplayClusterConfigViewport& viewport) const +{ + return GetItem(CfgViewports, idx, viewport, FString("GetViewport")); +} + +bool FDisplayClusterConfigManager::GetViewport(const FString& id, FDisplayClusterConfigViewport& viewport) const +{ + return GetItem(CfgViewports, id, viewport, FString("GetViewport")); +} + +//@todo: remove all GetLocal* functions. Config manager doesn't have to know its place in cluster. +bool FDisplayClusterConfigManager::GetLocalViewport(FDisplayClusterConfigViewport& viewport) const +{ + FDisplayClusterConfigClusterNode localNode; + if (GetLocalClusterNode(localNode)) + { + return GetItem(CfgViewports, localNode.ViewportId, viewport, FString("GetLocalViewport")); + } + + return false; +} + + +// Scene nodes +TArray FDisplayClusterConfigManager::GetSceneNodes() const +{ + return CfgSceneNodes; +} + +int32 FDisplayClusterConfigManager::GetSceneNodesAmount() const +{ + return static_cast(CfgSceneNodes.Num()); +} + +bool FDisplayClusterConfigManager::GetSceneNode(int32 idx, FDisplayClusterConfigSceneNode& actor) const +{ + return GetItem(CfgSceneNodes, idx, actor, FString("GetActor")); +} + +bool FDisplayClusterConfigManager::GetSceneNode(const FString& id, FDisplayClusterConfigSceneNode& actor) const +{ + return GetItem(CfgSceneNodes, id, actor, FString("GetActor")); +} + + +// Input devices +TArray FDisplayClusterConfigManager::GetInputDevices() const +{ + return CfgInputDevices; +} + +int32 FDisplayClusterConfigManager::GetInputDevicesAmount() const +{ + return CfgInputDevices.Num(); +} + +bool FDisplayClusterConfigManager::GetInputDevice(int32 idx, FDisplayClusterConfigInput& input) const +{ + return GetItem(CfgInputDevices, idx, input, FString("GetInputDevice")); +} + +bool FDisplayClusterConfigManager::GetInputDevice(const FString& id, FDisplayClusterConfigInput& input) const +{ + return GetItem(CfgInputDevices, id, input, FString("GetInputDevice")); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterConfigParserListener +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterConfigManager::AddClusterNode(const FDisplayClusterConfigClusterNode& cfgCNode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found cluster node: %s"), *cfgCNode.ToString()); + CfgClusterNodes.Add(cfgCNode); +} + +void FDisplayClusterConfigManager::AddScreen(const FDisplayClusterConfigScreen& cfgScreen) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found screen: %s"), *cfgScreen.ToString()); + CfgScreens.Add(cfgScreen); +} + +void FDisplayClusterConfigManager::AddViewport(const FDisplayClusterConfigViewport& cfgViewport) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found viewport: %s"), *cfgViewport.ToString()); + CfgViewports.Add(cfgViewport); +} + +void FDisplayClusterConfigManager::AddCamera(const FDisplayClusterConfigCamera& cfgCamera) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found camera: %s"), *cfgCamera.ToString()); + CfgCameras.Add(cfgCamera); +} + +void FDisplayClusterConfigManager::AddSceneNode(const FDisplayClusterConfigSceneNode& cfgSNode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found scene node: %s"), *cfgSNode.ToString()); + CfgSceneNodes.Add(cfgSNode); +} + +void FDisplayClusterConfigManager::AddInput(const FDisplayClusterConfigInput& cfgInput) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found input device: %s"), *cfgInput.ToString()); + CfgInputDevices.Add(cfgInput); +} + +void FDisplayClusterConfigManager::AddGeneral(const FDisplayClusterConfigGeneral& cfgGeneral) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found general: %s"), *cfgGeneral.ToString()); + CfgGeneral = cfgGeneral; +} + +void FDisplayClusterConfigManager::AddRender(const FDisplayClusterConfigRender& cfgRender) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found render: %s"), *cfgRender.ToString()); + CfgRender = cfgRender; +} + +void FDisplayClusterConfigManager::AddStereo(const FDisplayClusterConfigStereo& cfgStereo) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found stereo: %s"), *cfgStereo.ToString()); + CfgStereo = cfgStereo; +} + +void FDisplayClusterConfigManager::AddDebug(const FDisplayClusterConfigDebug& cfgDebug) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found debug: %s"), *cfgDebug.ToString()); + CfgDebug = cfgDebug; +} + +void FDisplayClusterConfigManager::AddCustom(const FDisplayClusterConfigCustom& cfgCustom) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Found custom: %s"), *cfgCustom.ToString()); + CfgCustom = cfgCustom; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigManager +////////////////////////////////////////////////////////////////////////////////////////////// +FDisplayClusterConfigManager::EConfigFileType FDisplayClusterConfigManager::GetConfigFileType(const FString& cfgPath) const +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + if (cfgPath == DisplayClusterStrings::misc::DbgStubConfig) + { + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Debug auto config requested")); + return EConfigFileType::DebugAuto; + } +#endif + + const FString ext = FPaths::GetExtension(cfgPath).ToLower(); + if (ext == FString(DisplayClusterStrings::cfg::file::FileExtXml).ToLower()) + { + UE_LOG(LogDisplayClusterConfig, Log, TEXT("XML config: %s"), *cfgPath); + return EConfigFileType::Xml; + } + else if ( + ext == FString(DisplayClusterStrings::cfg::file::FileExtCfg1).ToLower() || + ext == FString(DisplayClusterStrings::cfg::file::FileExtCfg2).ToLower() || + ext == FString(DisplayClusterStrings::cfg::file::FileExtCfg3).ToLower() || + ext == FString(DisplayClusterStrings::cfg::file::FileExtTxt).ToLower()) + { + UE_LOG(LogDisplayClusterConfig, Log, TEXT("TXT config: %s"), *cfgPath); + return EConfigFileType::Text; + } + + UE_LOG(LogDisplayClusterConfig, Warning, TEXT("Unknown file extension: %s"), *ext); + return EConfigFileType::Unknown; +} + +bool FDisplayClusterConfigManager::LoadConfig(const FString& cfgPath) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + + // Actually the data is reset on EndFrame. This one is a safety call. + ResetConfigData(); + +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + if (cfgPath.Compare(FString(DisplayClusterStrings::misc::DbgStubConfig), ESearchCase::IgnoreCase) != 0 && + FPaths::FileExists(cfgPath) == false) + { + UE_LOG(LogDisplayClusterConfig, Error, TEXT("File not found: %s"), *cfgPath); + return false; + } +#else + if (FPaths::FileExists(cfgPath) == false) + { + UE_LOG(LogDisplayClusterConfig, Error, TEXT("File not found: %s"), *cfgPath); + return false; + } +#endif + + // Instantiate appropriate parser + TUniquePtr parser; + switch (GetConfigFileType(cfgPath)) + { + case EConfigFileType::Text: + parser.Reset(new FDisplayClusterConfigParserText(this)); + break; + + case EConfigFileType::Xml: + parser.Reset(new FDisplayClusterConfigParserXml(this)); + break; + +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + case EConfigFileType::DebugAuto: + bIsDebugAuto = true; + parser.Reset(new FDisplayClusterConfigParserDebugAuto(this)); + break; +#endif + + default: + UE_LOG(LogDisplayClusterConfig, Error, TEXT("Unknown config type")); + return false; + } + + return parser->ParseFile(cfgPath); +} + +void FDisplayClusterConfigManager::ResetConfigData() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterConfig); + + CfgClusterNodes.Reset(); + CfgScreens.Reset(); + CfgViewports.Reset(); + CfgCameras.Reset(); + CfgSceneNodes.Reset(); + CfgInputDevices.Reset(); + + CfgGeneral = FDisplayClusterConfigGeneral(); + CfgStereo = FDisplayClusterConfigStereo(); + CfgRender = FDisplayClusterConfigRender(); + CfgDebug = FDisplayClusterConfigDebug(); + CfgCustom = FDisplayClusterConfigCustom(); +} + +template +bool FDisplayClusterConfigManager::GetItem(const TArray& container, uint32 idx, DataType& item, const FString& logHeader) const +{ + if (idx >= static_cast(container.Num())) + { + UE_LOG(LogDisplayClusterConfig, Error, TEXT("%s: index is out of bound <%d>"), *logHeader, idx); + return false; + } + + item = container[static_cast(idx)]; + return true; +} + +template +bool FDisplayClusterConfigManager::GetItem(const TArray& container, const FString& id, DataType& item, const FString& logHeader) const +{ + auto pFound = container.FindByPredicate([id](const DataType& _item) + { + return _item.Id == id; + }); + + if (!pFound) + { + UE_LOG(LogDisplayClusterConfig, Warning, TEXT("%s: ID not found <%s>"), *logHeader, *id); + return false; + } + + item = *pFound; + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigManager.h new file mode 100644 index 000000000000..49a644388e32 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigManager.h @@ -0,0 +1,155 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IPDisplayClusterConfigManager.h" + +#include "Parser/IDisplayClusterConfigParserListener.h" + +#include "DisplayClusterBuildConfig.h" + + +class FDisplayClusterConfigParser; + + +/** + * Config manager. Responsible for loading data from config file and providing with it to any other class. + */ +class FDisplayClusterConfigManager + : public IPDisplayClusterConfigManager + , protected IDisplayClusterConfigParserListener +{ +public: + FDisplayClusterConfigManager(); + virtual ~FDisplayClusterConfigManager(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Init(EDisplayClusterOperationMode OperationMode) override; + virtual void Release() override; + virtual bool StartSession(const FString& configPath, const FString& nodeId) override; + virtual void EndSession() override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterConfigManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual TArray GetClusterNodes() const override; + virtual int32 GetClusterNodesAmount() const override; + virtual bool GetClusterNode(int32 idx, FDisplayClusterConfigClusterNode& node) const override; + virtual bool GetClusterNode(const FString& id, FDisplayClusterConfigClusterNode& node) const override; + virtual bool GetMasterClusterNode(FDisplayClusterConfigClusterNode& node) const override; + virtual bool GetLocalClusterNode(FDisplayClusterConfigClusterNode& node) const override; + + virtual TArray GetScreens() const override; + virtual int32 GetScreensAmount() const override; + virtual bool GetScreen(int32 idx, FDisplayClusterConfigScreen& screen) const override; + virtual bool GetScreen(const FString& id, FDisplayClusterConfigScreen& screen) const override; + virtual bool GetLocalScreen(FDisplayClusterConfigScreen& screen) const override; + + virtual TArray GetCameras() const override; + virtual int32 GetCamerasAmount() const override; + virtual bool GetCamera(int32 idx, FDisplayClusterConfigCamera& camera) const override; + virtual bool GetCamera(const FString& id, FDisplayClusterConfigCamera& camera) const override; + + virtual TArray GetViewports() const override; + virtual int32 GetViewportsAmount() const override; + virtual bool GetViewport(int32 idx, FDisplayClusterConfigViewport& viewport) const override; + virtual bool GetViewport(const FString& id, FDisplayClusterConfigViewport& viewport) const override; + virtual bool GetLocalViewport(FDisplayClusterConfigViewport& screen) const override; + + virtual TArray GetSceneNodes() const override; + virtual int32 GetSceneNodesAmount() const override; + virtual bool GetSceneNode(int32 idx, FDisplayClusterConfigSceneNode& actor) const override; + virtual bool GetSceneNode(const FString& id, FDisplayClusterConfigSceneNode& actor) const override; + + virtual TArray GetInputDevices() const override; + virtual int32 GetInputDevicesAmount() const override; + virtual bool GetInputDevice(int32 idx, FDisplayClusterConfigInput& input) const override; + virtual bool GetInputDevice(const FString& id, FDisplayClusterConfigInput& input) const override; + + virtual FDisplayClusterConfigGeneral GetConfigGeneral() const override + { return CfgGeneral; } + + virtual FDisplayClusterConfigStereo GetConfigStereo() const override + { return CfgStereo; } + + virtual FDisplayClusterConfigRender GetConfigRender() const override + { return CfgRender; } + + virtual FDisplayClusterConfigDebug GetConfigDebug() const override + { return CfgDebug; } + + virtual FDisplayClusterConfigCustom GetConfigCustom() const override + { return CfgCustom; } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterConfigManager + ////////////////////////////////////////////////////////////////////////////////////////////// +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + virtual bool IsRunningDebugAuto() const override + { return bIsDebugAuto; } +#endif + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterConfigParserListener + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void AddClusterNode(const FDisplayClusterConfigClusterNode& cfgCNode) override; + virtual void AddScreen(const FDisplayClusterConfigScreen& cfgScreen) override; + virtual void AddViewport(const FDisplayClusterConfigViewport& cfgViewport) override; + virtual void AddCamera(const FDisplayClusterConfigCamera& cfgCamera) override; + virtual void AddSceneNode(const FDisplayClusterConfigSceneNode& cfgSNode) override; + virtual void AddGeneral(const FDisplayClusterConfigGeneral& cfgGeneral) override; + virtual void AddRender(const FDisplayClusterConfigRender& cfgRender) override; + virtual void AddStereo(const FDisplayClusterConfigStereo& cfgStereo) override; + virtual void AddDebug(const FDisplayClusterConfigDebug& cfgDebug) override; + virtual void AddInput(const FDisplayClusterConfigInput& cfgInput) override; + virtual void AddCustom(const FDisplayClusterConfigCustom& cfgCustom) override; + +private: + enum class EConfigFileType + { + Unknown, +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + DebugAuto, +#endif + Text, + Xml + }; + + template + bool GetItem(const TArray& container, uint32 idx, DataType& item, const FString& logHeader) const; + + template + bool GetItem(const TArray& container, const FString& id, DataType& item, const FString& logHeader) const; + + EConfigFileType GetConfigFileType(const FString& cfgPath) const; + bool LoadConfig(const FString& cfgPath); + void ResetConfigData(); + +private: + FString ConfigPath; + FString ClusterNodeId; + + TArray CfgClusterNodes; + TArray CfgScreens; + TArray CfgViewports; + TArray CfgCameras; + TArray CfgSceneNodes; + TArray CfgInputDevices; + + FDisplayClusterConfigGeneral CfgGeneral; + FDisplayClusterConfigStereo CfgStereo; + FDisplayClusterConfigRender CfgRender; + FDisplayClusterConfigDebug CfgDebug; + FDisplayClusterConfigCustom CfgCustom; + +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + bool bIsDebugAuto = false; +#endif +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigTypes.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigTypes.cpp new file mode 100644 index 000000000000..0e01ef25cd12 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/DisplayClusterConfigTypes.cpp @@ -0,0 +1,299 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Config/DisplayClusterConfigTypes.h" +#include "DisplayClusterStrings.h" +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/DisplayClusterLog.h" + + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigClusterNode +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigClusterNode::ToString() const +{ + return FString::Printf(TEXT("[%s + %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%d, %s=%d, %s=%s]"), + *FDisplayClusterConfigBase::ToString(), + DisplayClusterStrings::cfg::data::Id, *Id, + DisplayClusterStrings::cfg::data::cluster::Addr, *Addr, + DisplayClusterStrings::cfg::data::cluster::Master, DisplayClusterHelpers::str::BoolToStr(IsMaster), + DisplayClusterStrings::cfg::data::cluster::Screen, *ScreenId, + DisplayClusterStrings::cfg::data::cluster::Viewport, *ViewportId, + DisplayClusterStrings::cfg::data::cluster::PortCS, Port_CS, + DisplayClusterStrings::cfg::data::cluster::PortSS, Port_SS, + DisplayClusterStrings::cfg::data::cluster::Sound, DisplayClusterHelpers::str::BoolToStr(SoundEnabled)); +} + +bool FDisplayClusterConfigClusterNode::DeserializeFromString(const FString& line) +{ + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::Id), Id); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::cluster::Screen), ScreenId); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::cluster::Viewport), ViewportId); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::cluster::Master), IsMaster); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::cluster::Addr), Addr); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::cluster::PortCS), Port_CS); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::cluster::PortSS), Port_SS); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::cluster::Sound), SoundEnabled); + return FDisplayClusterConfigBase::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigViewport +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigViewport::ToString() const +{ + return FString::Printf(TEXT("[%s + %s=%s, %s=%s, %s=%d, %s=%d, %s=%s, %s=%s]"), + *FDisplayClusterConfigBase::ToString(), + DisplayClusterStrings::cfg::data::Id, *Id, + DisplayClusterStrings::cfg::data::Loc, *Loc.ToString(), + DisplayClusterStrings::cfg::data::viewport::Width, Size.X, + DisplayClusterStrings::cfg::data::viewport::Height, Size.Y, + DisplayClusterStrings::cfg::data::viewport::FlipH, DisplayClusterHelpers::str::BoolToStr(FlipHorizontal), + DisplayClusterStrings::cfg::data::viewport::FlipV, DisplayClusterHelpers::str::BoolToStr(FlipVertical)); +} + +bool FDisplayClusterConfigViewport::DeserializeFromString(const FString& line) +{ + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::Id), Id); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::viewport::PosX), Loc.X); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::viewport::PosY), Loc.Y); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::viewport::Width), Size.X); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::viewport::Height), Size.Y); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::viewport::FlipH), FlipHorizontal); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::viewport::FlipV), FlipVertical); + return FDisplayClusterConfigBase::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigSceneNode +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigSceneNode::ToString() const +{ + return FString::Printf(TEXT("[%s + %s=%s, %s=%s, %s=%s, %s=%s, %s=%s, %s=%d]"), + *FDisplayClusterConfigBase::ToString(), + DisplayClusterStrings::cfg::data::Id, *Id, + DisplayClusterStrings::cfg::data::ParentId, *ParentId, + DisplayClusterStrings::cfg::data::Loc, *Loc.ToString(), + DisplayClusterStrings::cfg::data::Rot, *Rot.ToString(), + DisplayClusterStrings::cfg::data::scene::TrackerId, *TrackerId, + DisplayClusterStrings::cfg::data::scene::TrackerCh, TrackerCh); +} + +bool FDisplayClusterConfigSceneNode::DeserializeFromString(const FString& line) +{ + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::Id), Id); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::ParentId), ParentId); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::Loc), Loc); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::Rot), Rot); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::scene::TrackerId), TrackerId); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::scene::TrackerCh), TrackerCh); + return FDisplayClusterConfigBase::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigScreen +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigScreen::ToString() const +{ + return FString::Printf(TEXT("[%s + %s=%s]"), + *FDisplayClusterConfigSceneNode::ToString(), + DisplayClusterStrings::cfg::data::screen::Size, *Size.ToString()); +} + +bool FDisplayClusterConfigScreen::DeserializeFromString(const FString& line) +{ + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::screen::Size), Size); + return FDisplayClusterConfigSceneNode::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigCamera +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigCamera::ToString() const +{ + return FString::Printf(TEXT("[%s + ]"), + *FDisplayClusterConfigSceneNode::ToString()); +} + +bool FDisplayClusterConfigCamera::DeserializeFromString(const FString& line) +{ + return FDisplayClusterConfigSceneNode::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigInput +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigInput::ToString() const +{ + return FString::Printf(TEXT("[%s + %s=%s, %s=%s, %s={%s}]"), + *FDisplayClusterConfigBase::ToString(), + DisplayClusterStrings::cfg::data::Id, *Id, + DisplayClusterStrings::cfg::data::input::Type, *Type, + TEXT("params"), *Params); +} + +bool FDisplayClusterConfigInput::DeserializeFromString(const FString& line) +{ + // Save full string to allow an input device to parse (polymorphic) + Params = line; + FString mapping; + + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::Id), Id); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::input::Type), Type); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::input::Remap), mapping); + + DisplayClusterHelpers::str::DustCommandLineValue(mapping); + + TArray pairs; + FString pair; + while (mapping.Split(FString(","), &pair, &mapping, ESearchCase::IgnoreCase, ESearchDir::FromStart)) + { + pairs.Add(pair); + } + + pairs.Add(mapping); + + for (const auto& item : pairs) + { + FString strL, strR; + + if (item.Split(FString(":"), &strL, &strR, ESearchCase::IgnoreCase, ESearchDir::FromStart)) + { + const int32 l = FDisplayClusterTypesConverter::FromString(strL); + const int32 r = FDisplayClusterTypesConverter::FromString(strR); + + if (l != r) + { + ChMap.Add(l, r); + } + } + } + + return FDisplayClusterConfigBase::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigGeneral +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigGeneral::ToString() const +{ + return FString::Printf(TEXT("[%s + %s=%d]"), + *FDisplayClusterConfigBase::ToString(), + DisplayClusterStrings::cfg::data::general::SwapSyncPolicy, SwapSyncPolicy); +} + +bool FDisplayClusterConfigGeneral::DeserializeFromString(const FString& line) +{ + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::general::SwapSyncPolicy), SwapSyncPolicy); + return FDisplayClusterConfigBase::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigRender +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigRender::ToString() const +{ + return FString::Printf(TEXT("%s + "), + *FDisplayClusterConfigBase::ToString()); +} + +bool FDisplayClusterConfigRender::DeserializeFromString(const FString& line) +{ + return FDisplayClusterConfigBase::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigStereo +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigStereo::ToString() const +{ + return FString::Printf(TEXT("[%s + %s=%s, %s=%f]"), + *FDisplayClusterConfigBase::ToString(), + DisplayClusterStrings::cfg::data::stereo::EyeSwap, DisplayClusterHelpers::str::BoolToStr(EyeSwap), + DisplayClusterStrings::cfg::data::stereo::EyeDist, EyeDist); +} + +bool FDisplayClusterConfigStereo::DeserializeFromString(const FString& line) +{ + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::stereo::EyeDist), EyeDist); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::stereo::EyeSwap), EyeSwap); + return FDisplayClusterConfigBase::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigDebug +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigDebug::ToString() const +{ + return FString::Printf(TEXT("[%s + %s=%s, %s=%s, %s=%f]"), + *FDisplayClusterConfigBase::ToString(), + DisplayClusterStrings::cfg::data::debug::DrawStats, DisplayClusterHelpers::str::BoolToStr(DrawStats), + DisplayClusterStrings::cfg::data::debug::LagSim, DisplayClusterHelpers::str::BoolToStr(LagSimulateEnabled), + DisplayClusterStrings::cfg::data::debug::LagTime, LagMaxTime); +} + +bool FDisplayClusterConfigDebug::DeserializeFromString(const FString& line) +{ + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::debug::DrawStats), DrawStats); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::debug::LagSim), LagSimulateEnabled); + DisplayClusterHelpers::str::ExtractCommandLineValue(line, FString(DisplayClusterStrings::cfg::data::debug::LagTime), LagMaxTime); + return FDisplayClusterConfigBase::DeserializeFromString(line); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterConfigCustom +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterConfigCustom::ToString() const +{ + FString str = FDisplayClusterConfigBase::ToString() + FString( + "["); + int i = 0; + + for (auto it = Args.CreateConstIterator(); it; ++it) + { + str += FString::Printf(TEXT("\nCustom argument %d: %s=%s\n"), i++, *it->Key, *it->Value); + } + + str += FString("]"); + + return str; +} + +bool FDisplayClusterConfigCustom::DeserializeFromString(const FString& line) +{ + // Non-typical way of specifying custom arguments (we don't know + // the argument names) forces us to perform individual parsing approach. + FString tmpLine = line; + + // Prepare string before parsing + tmpLine.RemoveFromStart(DisplayClusterStrings::cfg::data::custom::Header); + tmpLine.TrimStartAndEndInline(); + + // Break into argument-value pairs + TArray pairs; + tmpLine.ParseIntoArray(pairs, TEXT(" ")); + + // Fill data from pairs + for (auto pair : pairs) + { + FString key, val; + if (pair.Split(FString(DisplayClusterStrings::strKeyValSeparator), &key, &val)) + { + if (key.Len() > 0 && val.Len() > 0) + { + Args.Add(key, val); + } + } + } + + return FDisplayClusterConfigBase::DeserializeFromString(line); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/IPDisplayClusterConfigManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/IPDisplayClusterConfigManager.h new file mode 100644 index 000000000000..87f6d7dbeae8 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/IPDisplayClusterConfigManager.h @@ -0,0 +1,24 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Config/IDisplayClusterConfigManager.h" +#include "IPDisplayClusterManager.h" + +#include "DisplayClusterBuildConfig.h" + + +/** + * Config manager private interface + */ +struct IPDisplayClusterConfigManager + : public IDisplayClusterConfigManager + , public IPDisplayClusterManager +{ + virtual ~IPDisplayClusterConfigManager() + { } + +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + virtual bool IsRunningDebugAuto() const = 0; +#endif +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParser.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParser.cpp new file mode 100644 index 000000000000..73258867820d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParser.cpp @@ -0,0 +1,80 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterConfigParser.h" + + +FDisplayClusterConfigParser::FDisplayClusterConfigParser(IDisplayClusterConfigParserListener* pListener) : + ConfigParserListener(pListener), + CurrentConfigPath() +{ +} + +FDisplayClusterConfigParser::~FDisplayClusterConfigParser() +{ +} + + +bool FDisplayClusterConfigParser::ParseFile(const FString& path) +{ + CurrentConfigPath = path; + return !CurrentConfigPath.IsEmpty(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterConfigParserListener +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterConfigParser::AddClusterNode(const FDisplayClusterConfigClusterNode& node) +{ + ConfigParserListener->AddClusterNode(node); +} + +void FDisplayClusterConfigParser::AddScreen(const FDisplayClusterConfigScreen& screen) +{ + ConfigParserListener->AddScreen(screen); +} + +void FDisplayClusterConfigParser::AddViewport(const FDisplayClusterConfigViewport& viewport) +{ + ConfigParserListener->AddViewport(viewport); +} + +void FDisplayClusterConfigParser::AddCamera(const FDisplayClusterConfigCamera& camera) +{ + ConfigParserListener->AddCamera(camera); +} + +void FDisplayClusterConfigParser::AddSceneNode(const FDisplayClusterConfigSceneNode& node) +{ + ConfigParserListener->AddSceneNode(node); +} + +void FDisplayClusterConfigParser::AddGeneral(const FDisplayClusterConfigGeneral& general) +{ + ConfigParserListener->AddGeneral(general); +} + +void FDisplayClusterConfigParser::AddRender(const FDisplayClusterConfigRender& render) +{ + ConfigParserListener->AddRender(render); +} + +void FDisplayClusterConfigParser::AddStereo(const FDisplayClusterConfigStereo& stereo) +{ + ConfigParserListener->AddStereo(stereo); +} + +void FDisplayClusterConfigParser::AddDebug(const FDisplayClusterConfigDebug& debug) +{ + ConfigParserListener->AddDebug(debug); +} + +void FDisplayClusterConfigParser::AddInput(const FDisplayClusterConfigInput& input) +{ + ConfigParserListener->AddInput(input); +} + +void FDisplayClusterConfigParser::AddCustom(const FDisplayClusterConfigCustom& custom) +{ + ConfigParserListener->AddCustom(custom); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParser.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParser.h new file mode 100644 index 000000000000..85d6af36b2cd --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParser.h @@ -0,0 +1,44 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IDisplayClusterConfigParserListener.h" +#include "Config/DisplayClusterConfigTypes.h" + + +/** + * Abstract config parser + */ +class FDisplayClusterConfigParser + : protected IDisplayClusterConfigParserListener +{ +public: + explicit FDisplayClusterConfigParser(IDisplayClusterConfigParserListener* pListener); + virtual ~FDisplayClusterConfigParser() = 0; + +public: + // Entry point for file parsing + virtual bool ParseFile(const FString& path); + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterConfigParserListener + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void AddClusterNode(const FDisplayClusterConfigClusterNode& node) override final; + virtual void AddScreen(const FDisplayClusterConfigScreen& screen) override final; + virtual void AddViewport(const FDisplayClusterConfigViewport& viewport) override final; + virtual void AddCamera(const FDisplayClusterConfigCamera& camera) override final; + virtual void AddSceneNode(const FDisplayClusterConfigSceneNode& node) override final; + virtual void AddGeneral(const FDisplayClusterConfigGeneral& general) override final; + virtual void AddRender(const FDisplayClusterConfigRender& render) override final; + virtual void AddStereo(const FDisplayClusterConfigStereo& stereo) override final; + virtual void AddDebug(const FDisplayClusterConfigDebug& debug) override final; + virtual void AddInput(const FDisplayClusterConfigInput& input) override final; + virtual void AddCustom(const FDisplayClusterConfigCustom& custom) override final; + +private: + IDisplayClusterConfigParserListener* const ConfigParserListener; + FString CurrentConfigPath; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserDebugAuto.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserDebugAuto.cpp new file mode 100644 index 000000000000..e05b9b38e49c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserDebugAuto.cpp @@ -0,0 +1,64 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterConfigParserDebugAuto.h" + +#include "DisplayClusterBuildConfig.h" +#include "DisplayClusterConstants.h" +#include "DisplayClusterStrings.h" +#include "Config/DisplayClusterConfigTypes.h" + + +FDisplayClusterConfigParserDebugAuto::FDisplayClusterConfigParserDebugAuto(IDisplayClusterConfigParserListener* pListener) : + FDisplayClusterConfigParser(pListener) +{ +} + +bool FDisplayClusterConfigParserDebugAuto::ParseFile(const FString& path) +{ +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + FDisplayClusterConfigClusterNode ClusterNode; + ClusterNode.Id = DisplayClusterStrings::misc::DbgStubNodeId; + ClusterNode.IsMaster = true; + ClusterNode.Addr = TEXT("127.0.0.1"); + ClusterNode.Port_CS = 41001; + ClusterNode.Port_SS = 41002; + ClusterNode.ScreenId = TEXT("screen_stub");; + ClusterNode.ViewportId = TEXT("viewport_stub"); + ClusterNode.SoundEnabled = true; + AddClusterNode(ClusterNode); + + const float PixelDensity = 0.6f / 1920.f; + + FDisplayClusterConfigScreen Screen; + Screen.Id = ClusterNode.ScreenId; + Screen.Loc = FVector(0.7f, 0.f, 0.f); + Screen.Rot = FRotator::ZeroRotator; + Screen.Size = FVector2D(PixelDensity * DisplayClusterConstants::misc::DebugAutoResX, PixelDensity * DisplayClusterConstants::misc::DebugAutoResY); + AddScreen(Screen); + + FDisplayClusterConfigViewport Viewport; + Viewport.Id = ClusterNode.ViewportId; + Viewport.Loc = FIntPoint(0, 0); + Viewport.Size = FIntPoint(DisplayClusterConstants::misc::DebugAutoResX, DisplayClusterConstants::misc::DebugAutoResY); + Viewport.FlipHorizontal = false; + Viewport.FlipVertical = false; + AddViewport(Viewport); + + FDisplayClusterConfigCamera Camera; + Camera.Id = TEXT("camera_stub"); + Camera.Loc = FVector::ZeroVector; + Camera.Rot = FRotator::ZeroRotator; + AddCamera(Camera); + + FDisplayClusterConfigGeneral General; + General.SwapSyncPolicy = 1; + AddGeneral(General); + + FDisplayClusterConfigStereo Stereo; + Stereo.EyeDist = 0.064f; + AddStereo(Stereo); +#endif // DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + + return true; +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserDebugAuto.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserDebugAuto.h new file mode 100644 index 000000000000..2f9deca5d2c6 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserDebugAuto.h @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterConfigParser.h" + + +/** + * Auxiliary config parser. It generates hard-coded config. + */ +class FDisplayClusterConfigParserDebugAuto + : public FDisplayClusterConfigParser +{ +public: + FDisplayClusterConfigParserDebugAuto(IDisplayClusterConfigParserListener* pListener); + +protected: + // Entry point for file parsing + virtual bool ParseFile(const FString& path) override; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserText.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserText.cpp new file mode 100644 index 000000000000..9bfff4eabca0 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserText.cpp @@ -0,0 +1,98 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterConfigParserText.h" +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" +#include "Misc/DisplayClusterLog.h" +#include "DisplayClusterStrings.h" + + +FDisplayClusterConfigParserText::FDisplayClusterConfigParserText(IDisplayClusterConfigParserListener* pListener) : + FDisplayClusterConfigParser(pListener) +{ +} + +bool FDisplayClusterConfigParserText::ParseFile(const FString& path) +{ + // Prepare path + FString cfgPath(path); + FPaths::NormalizeFilename(cfgPath); + + // Load data + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Parsing config file %s"), *cfgPath); + if (FPaths::FileExists(cfgPath)) + { + TArray data; + if (FFileHelper::LoadANSITextFileToStrings(*cfgPath, nullptr, data) == true) + { + // Parse each line from config + for (auto line : data) + { + line.TrimStartAndEndInline(); + ParseLine(line); + } + + // Parsed, complete on base + return FDisplayClusterConfigParser::ParseFile(path); + } + } + + // An error occurred + return false; +} + +void FDisplayClusterConfigParserText::ParseLine(const FString& line) +{ + if (line.IsEmpty() || line.StartsWith(FString(DisplayClusterStrings::cfg::spec::Comment))) + { + // Skip this line + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::cluster::Header))) + { + AddClusterNode(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::screen::Header))) + { + AddScreen(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::viewport::Header))) + { + AddViewport(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::camera::Header))) + { + AddCamera(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::scene::Header))) + { + AddSceneNode(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::general::Header))) + { + AddGeneral(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::render::Header))) + { + AddRender(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::stereo::Header))) + { + AddStereo(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::debug::Header))) + { + AddDebug(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::input::Header))) + { + AddInput(impl_parse(line)); + } + else if (line.StartsWith(FString(DisplayClusterStrings::cfg::data::custom::Header))) + { + AddCustom(impl_parse(line)); + } + else + { + UE_LOG(LogDisplayClusterConfig, Warning, TEXT("Unknown config token [%s]"), *line); + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserText.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserText.h new file mode 100644 index 000000000000..2c007261c1b6 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserText.h @@ -0,0 +1,37 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterConfigParser.h" +#include "Misc/DisplayClusterLog.h" + + +/** + * Config parser for text based config files + */ +class FDisplayClusterConfigParserText + : public FDisplayClusterConfigParser +{ +public: + FDisplayClusterConfigParserText(IDisplayClusterConfigParserListener* pListener); + +protected: + // Entry point for file parsing + virtual bool ParseFile(const FString& path) override; + + // Entry point for line parsing + void ParseLine(const FString& line); + +protected: + // Data type parsing + template + inline T impl_parse(const FString& line) + { + static_assert(std::is_base_of::value, "Only Display Cluster config types allowed"); + T tmp; bool result = static_cast(tmp).DeserializeFromString(line); + UE_LOG(LogDisplayClusterConfig, Log, TEXT("Deserialization: %s"), result ? TEXT("ok") : TEXT("failed")); + return tmp; + } +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserXml.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserXml.cpp new file mode 100644 index 000000000000..c1b803e79108 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserXml.cpp @@ -0,0 +1,13 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterConfigParserXml.h" + + +FDisplayClusterConfigParserXml::FDisplayClusterConfigParserXml(IDisplayClusterConfigParserListener* pListener) : + FDisplayClusterConfigParser(pListener) +{ +} + +//bool FDisplayClusterConfigParserXml::ReadConfigFile(const FString& path) +//{ +//} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserXml.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserXml.h new file mode 100644 index 000000000000..59973f3850bb --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/DisplayClusterConfigParserXml.h @@ -0,0 +1,28 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterConfigParser.h" + + +/** + * Config parser for XML based config files + */ +class FDisplayClusterConfigParserXml + : public FDisplayClusterConfigParser +{ +public: + FDisplayClusterConfigParserXml(IDisplayClusterConfigParserListener* pListener); + +public: + // Entry point for file parsing + virtual bool ParseFile(const FString& path) override + { + // Not implemented yet + return false; + } + +protected: + //virtual bool ReadConfigFile(const FString& path); +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/IDisplayClusterConfigParserListener.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/IDisplayClusterConfigParserListener.h new file mode 100644 index 000000000000..64bc03056542 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Config/Parser/IDisplayClusterConfigParserListener.h @@ -0,0 +1,28 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Config/DisplayClusterConfigTypes.h" + + +/** + * Interface for parser listener. Notifies about entities found in a config file. + */ +struct IDisplayClusterConfigParserListener +{ +public: + virtual ~IDisplayClusterConfigParserListener() + { } + + virtual void AddClusterNode(const FDisplayClusterConfigClusterNode& cnode) = 0; + virtual void AddScreen(const FDisplayClusterConfigScreen& screen) = 0; + virtual void AddViewport(const FDisplayClusterConfigViewport& viewport) = 0; + virtual void AddCamera(const FDisplayClusterConfigCamera& camera) = 0; + virtual void AddSceneNode(const FDisplayClusterConfigSceneNode& snode) = 0; + virtual void AddGeneral(const FDisplayClusterConfigGeneral& general) = 0; + virtual void AddRender(const FDisplayClusterConfigRender& render) = 0; + virtual void AddStereo(const FDisplayClusterConfigStereo& stereo) = 0; + virtual void AddDebug(const FDisplayClusterConfigDebug& debug) = 0; + virtual void AddInput(const FDisplayClusterConfigInput& input) = 0; + virtual void AddCustom(const FDisplayClusterConfigCustom& custom) = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterBuildConfig.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterBuildConfig.h new file mode 100644 index 000000000000..183968b93405 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterBuildConfig.h @@ -0,0 +1,13 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +// Enables automatic ID resolve by host address. This feature +// can be used only with single DisplayCluster instance per PC. +#define DISPLAY_CLUSTER_USE_AUTOMATIC_NODE_ID_RESOLVE + +// Allows to run game with stereo in easy way. You don't have +// to have a config file and a lot of command line arguments. +// Simple argument list would be: +// -dc_cluster -dc_cfg=? -quad_buffer_stereo -opengl4 +#define DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterConstants.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterConstants.h new file mode 100644 index 000000000000..48ccc09be248 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterConstants.h @@ -0,0 +1,30 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterBuildConfig.h" + + +namespace DisplayClusterConstants +{ + namespace net + { + static constexpr int32 ClientConnectTriesAmount = 10; // times + static constexpr float ClientConnectRetryDelay = 1.0f; // sec + static constexpr uint32 BarrierGameStartWaitTimeout = 20000; // ms + static constexpr uint32 BarrierWaitTimeout = 5000; // ms + static constexpr int32 SocketBufferSize = INT16_MAX; // bytes + static constexpr int32 MessageBufferSize = INT16_MAX; // bytes + }; + + namespace misc + { +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + static constexpr int32 DebugAutoWinX = 0; + static constexpr int32 DebugAutoWinY = 0; + static constexpr int32 DebugAutoResX = 1920; + static constexpr int32 DebugAutoResY = 1080; +#endif + } +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterGlobals.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterGlobals.cpp new file mode 100644 index 000000000000..f46eb28c7ce1 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterGlobals.cpp @@ -0,0 +1,7 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterGlobals.h" + + +IPDisplayCluster* GDisplayCluster = nullptr; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterGlobals.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterGlobals.h new file mode 100644 index 000000000000..d51408454930 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterGlobals.h @@ -0,0 +1,9 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +struct IPDisplayCluster; + + +// Internal global DisplayCluster module interface +extern IPDisplayCluster* GDisplayCluster; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterModule.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterModule.cpp new file mode 100644 index 000000000000..7780d13442d3 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterModule.cpp @@ -0,0 +1,201 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterModule.h" + +#include "Cluster/DisplayClusterClusterManager.h" +#include "Config/DisplayClusterConfigManager.h" +#include "Game/DisplayClusterGameManager.h" +#include "Input/DisplayClusterInputManager.h" +#include "Render/DisplayClusterRenderManager.h" + +#include "Misc/DisplayClusterLog.h" + +#include "DisplayClusterGlobals.h" + + +FDisplayClusterModule::FDisplayClusterModule() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + GDisplayCluster = this; +} + +FDisplayClusterModule::~FDisplayClusterModule() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + +#if 1 + GDisplayCluster = nullptr; +#else + // WORKAROUND + // UE4 does something like that: + // 1. inst1 = new FDisplayClusterModule + // 2. inst2 = new FDisplayClusterModule + // 3. delete inst1 + // To store valid pointer (inst2) I need the check below. + if (GDisplayCluster == this) + { + GDisplayCluster = nullptr; + } +#endif +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IModuleInterface +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterModule::StartupModule() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + UE_LOG(LogDisplayClusterModule, Log, TEXT("DisplayCluster module has been started")); +} + +void FDisplayClusterModule::ShutdownModule() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + // Clean everything before .dtor call + Release(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayCluster +////////////////////////////////////////////////////////////////////////////////////////////// + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterModule::Init(EDisplayClusterOperationMode OperationMode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + UE_LOG(LogDisplayClusterModule, Log, TEXT("Instantiating subsystem managers...")); + + CurrentOperationMode = OperationMode; + + // Initialize internals (the order is important) + Managers.Add(MgrConfig = new FDisplayClusterConfigManager); + Managers.Add(MgrRender = new FDisplayClusterRenderManager); + Managers.Add(MgrCluster = new FDisplayClusterClusterManager); + Managers.Add(MgrInput = new FDisplayClusterInputManager); + Managers.Add(MgrGame = new FDisplayClusterGameManager); + + UE_LOG(LogDisplayClusterModule, Log, TEXT("Initializing subsystems to %s operation mode"), *FDisplayClusterTypesConverter::ToString(CurrentOperationMode)); + + bool result = true; + auto it = Managers.CreateIterator(); + while (result && it) + { + result = result && (*it)->Init(CurrentOperationMode); + ++it; + } + + if (!result) + { + UE_LOG(LogDisplayClusterModule, Error, TEXT("An error occurred during internal initialization")); + } + + return result; +} + +void FDisplayClusterModule::Release() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + UE_LOG(LogDisplayClusterModule, Log, TEXT("Cleaning up internals...")); + + for (auto pMgr : Managers) + { + pMgr->Release(); + delete pMgr; + } + + Managers.Empty(); +} + +bool FDisplayClusterModule::StartSession(const FString& configPath, const FString& nodeId) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + UE_LOG(LogDisplayClusterModule, Log, TEXT("StartSession: config path is %s"), *configPath); + + bool result = true; + auto it = Managers.CreateIterator(); + while (result && it) + { + result = result && (*it)->StartSession(configPath, nodeId); + ++it; + } + + if (!result) + { + UE_LOG(LogDisplayClusterModule, Error, TEXT("An error occurred during session start")); + } + + return result; +} + +void FDisplayClusterModule::EndSession() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + UE_LOG(LogDisplayClusterModule, Log, TEXT("Stopping DisplayCluster session...")); + + for (auto pMgr : Managers) + { + pMgr->EndSession(); + } +} + +bool FDisplayClusterModule::StartScene(UWorld* pWorld) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + UE_LOG(LogDisplayClusterModule, Log, TEXT("Starting game...")); + + check(pWorld); + + bool result = true; + auto it = Managers.CreateIterator(); + while (result && it) + { + result = result && (*it)->StartScene(pWorld); + ++it; + } + + if (!result) + { + UE_LOG(LogDisplayClusterModule, Error, TEXT("An error occurred during game (level) start")); + } + + return result; +} + +void FDisplayClusterModule::EndScene() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + UE_LOG(LogDisplayClusterModule, Log, TEXT("Stopping game...")); + + for (auto pMgr : Managers) + { + pMgr->EndScene(); + } +} + +void FDisplayClusterModule::PreTick(float DeltaSeconds) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterModule); + + UE_LOG(LogDisplayClusterModule, Verbose, TEXT("PreTick: delta time - %f"), DeltaSeconds); + + for (auto pMgr : Managers) + { + pMgr->PreTick(DeltaSeconds); + } +} + +IMPLEMENT_MODULE(FDisplayClusterModule, DisplayCluster) diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterModule.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterModule.h new file mode 100644 index 000000000000..82996b2c62cd --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterModule.h @@ -0,0 +1,86 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IPDisplayCluster.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Config/IPDisplayClusterConfigManager.h" +#include "Game/IPDisplayClusterGameManager.h" +#include "Input/IPDisplayClusterInputManager.h" +#include "Render/IPDisplayClusterRenderManager.h" + + +/** + * Display Cluster module implementation + */ +class FDisplayClusterModule : + public IPDisplayCluster +{ +public: + FDisplayClusterModule(); + virtual ~FDisplayClusterModule(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayCluster + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual IDisplayClusterRenderManager* GetRenderMgr() const override { return MgrRender; } + virtual IDisplayClusterClusterManager* GetClusterMgr() const override { return MgrCluster; } + virtual IDisplayClusterInputManager* GetInputMgr() const override { return MgrInput; } + virtual IDisplayClusterConfigManager* GetConfigMgr() const override { return MgrConfig; } + virtual IDisplayClusterGameManager* GetGameMgr() const override { return MgrGame; } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayCluster + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual IPDisplayClusterRenderManager* GetPrivateRenderMgr() const override { return MgrRender; } + virtual IPDisplayClusterClusterManager* GetPrivateClusterMgr() const override { return MgrCluster; } + virtual IPDisplayClusterInputManager* GetPrivateInputMgr() const override { return MgrInput; } + virtual IPDisplayClusterConfigManager* GetPrivateConfigMgr() const override { return MgrConfig; } + virtual IPDisplayClusterGameManager* GetPrivateGameMgr() const override { return MgrGame; } + + virtual EDisplayClusterOperationMode GetOperationMode() const override + { return CurrentOperationMode; } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Init(EDisplayClusterOperationMode OperationMode) override; + virtual void Release() override; + virtual bool StartSession(const FString& configPath, const FString& nodeId) override; + virtual void EndSession() override; + virtual bool StartScene(UWorld* pWorld) override; + virtual void EndScene() override; + virtual void PreTick(float DeltaSeconds) override; + +private: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IModuleInterface + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void StartupModule() override; + virtual void ShutdownModule() override; +#if 0 + virtual void PreUnloadCallback() override; + virtual void PostLoadCallback() override; + virtual bool SupportsDynamicReloading() override; + virtual bool SupportsAutomaticShutdown() override; + virtual bool IsGameModule() const override; +#endif + +private: + // DisplayCluster subsystems + IPDisplayClusterClusterManager* MgrCluster = nullptr; + IPDisplayClusterRenderManager* MgrRender = nullptr; + IPDisplayClusterInputManager* MgrInput = nullptr; + IPDisplayClusterConfigManager* MgrConfig = nullptr; + IPDisplayClusterGameManager* MgrGame = nullptr; + + // Array of available managers + TArray Managers; + + // Runtime + EDisplayClusterOperationMode CurrentOperationMode = EDisplayClusterOperationMode::Disabled; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterStrings.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterStrings.h new file mode 100644 index 000000000000..ab1bf7ec7c3a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/DisplayClusterStrings.h @@ -0,0 +1,193 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterBuildConfig.h" + + +namespace DisplayClusterStrings +{ + // Common strings + static constexpr auto strKeyValSeparator = TEXT("="); + + // Command line arguments + namespace args + { + static constexpr auto Cluster = TEXT("dc_cluster"); + static constexpr auto Standalone = TEXT("dc_standalone"); + static constexpr auto Node = TEXT("dc_node"); + static constexpr auto Config = TEXT("dc_cfg"); + static constexpr auto Camera = TEXT("dc_camera"); + + // Stereo device types (command line values) + namespace dev + { + static constexpr auto Debug = TEXT("dc_dev_debug"); + static constexpr auto QBS = TEXT("quad_buffer_stereo"); + static constexpr auto TB = TEXT("dc_dev_top_bottom"); + static constexpr auto SbS = TEXT("dc_dev_side_by_side"); + static constexpr auto Mono = TEXT("dc_dev_mono"); + } + } + + namespace cfg + { + // Config file extensions + namespace file + { + static constexpr auto FileExtCfg1 = TEXT("cfg"); + static constexpr auto FileExtCfg2 = TEXT("conf"); + static constexpr auto FileExtCfg3 = TEXT("config"); + static constexpr auto FileExtTxt = TEXT("txt"); + static constexpr auto FileExtXml = TEXT("xml"); + } + + // Config special constants + namespace spec + { + static constexpr auto Comment = TEXT("#"); + static constexpr auto KeyValSeparator = TEXT("="); + static constexpr auto ValTrue = TEXT("true"); + static constexpr auto ValFalse = TEXT("false"); + static constexpr auto MappingDelimiter = TEXT(","); + } + + // Config data tokens + namespace data + { + static constexpr auto Id = TEXT("id"); + static constexpr auto ParentId = TEXT("parent"); + static constexpr auto Loc = TEXT("loc"); + static constexpr auto Rot = TEXT("rot"); + + // Cluster tokens + namespace cluster + { + static constexpr auto Header = TEXT("[cluster_node]"); + static constexpr auto Addr = TEXT("addr"); + static constexpr auto Screen = TEXT("screen"); + static constexpr auto Viewport = TEXT("viewport"); + static constexpr auto PortCS = TEXT("port_cs"); + static constexpr auto PortSS = TEXT("port_ss"); + static constexpr auto Master = TEXT("master"); + static constexpr auto Sound = TEXT("sound"); + // + Id + } + + // Screen tokens + namespace screen + { + static constexpr auto Header = TEXT("[screen]"); + static constexpr auto Size = TEXT("size"); + // + Id, Parent, Loc, Rot + } + + // Viewport tokens + namespace viewport + { + static constexpr auto Header = TEXT("[viewport]"); + static constexpr auto PosX = TEXT("x"); + static constexpr auto PosY = TEXT("y"); + static constexpr auto Width = TEXT("width"); + static constexpr auto Height = TEXT("height"); + static constexpr auto FlipH = TEXT("flip_h"); + static constexpr auto FlipV = TEXT("flip_v"); + // + Id + } + + // Camera tokens + namespace camera + { + static constexpr auto Header = TEXT("[camera]"); + // + Id, Loc, Rot, Parent + } + + // Scene node (transforms) + namespace scene + { + static constexpr auto Header = TEXT("[scene_node]"); + static constexpr auto TrackerId = TEXT("tracker_id"); + static constexpr auto TrackerCh = TEXT("tracker_ch"); + // + Id, Loc, Rot, Parent + } + + // Input tokens + namespace input + { + static constexpr auto Header = TEXT("[input]"); + static constexpr auto Type = TEXT("type"); + static constexpr auto Address = TEXT("addr"); + static constexpr auto Remap = TEXT("remap"); + // + Id + + static constexpr auto Right = TEXT("right"); + static constexpr auto Front = TEXT("front"); + static constexpr auto Up = TEXT("up"); + + static constexpr auto MapX = TEXT("x"); + static constexpr auto MapNX = TEXT("-x"); + static constexpr auto MapY = TEXT("y"); + static constexpr auto MapNY = TEXT("-y"); + static constexpr auto MapZ = TEXT("z"); + static constexpr auto MapNZ = TEXT("-z"); + + static constexpr auto DeviceTracker = TEXT("tracker"); + static constexpr auto DeviceAnalog = TEXT("analog"); + static constexpr auto DeviceButtons = TEXT("buttons"); + } + + // General settings tokens + namespace general + { + static constexpr auto Header = TEXT("[general]"); + static constexpr auto SwapSyncPolicy = TEXT("swap_sync_policy"); + } + + // Stereo tokens + namespace stereo + { + static constexpr auto Header = TEXT("[stereo]"); + static constexpr auto EyeSwap = TEXT("eye_swap"); + static constexpr auto EyeDist = TEXT("eye_dist"); + } + + // Render tokens + namespace render + { + static constexpr auto Header = TEXT("[render]"); + } + + // Debug tokens + namespace debug + { + static constexpr auto Header = TEXT("[debug]"); + static constexpr auto LagSim = TEXT("lag_simulation"); + static constexpr auto LagTime = TEXT("lag_max_time"); + static constexpr auto DrawStats = TEXT("draw_stats"); + } + + // Custom arguments + namespace custom + { + static constexpr auto Header = TEXT("[custom]"); + static constexpr auto SwapInt = TEXT("swap_int"); + } + } + }; + + namespace rhi + { + static constexpr auto OpenGL = TEXT("OpenGL"); + static constexpr auto D3D11 = TEXT("D3D11"); + static constexpr auto D3D12 = TEXT("D3D12"); + } + + namespace misc + { +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + static constexpr auto DbgStubConfig = TEXT("?"); + static constexpr auto DbgStubNodeId = TEXT("node_stub"); +#endif + } +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameEngine.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameEngine.cpp new file mode 100644 index 000000000000..ebed5d8c6322 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameEngine.cpp @@ -0,0 +1,227 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterGameEngine.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Cluster/Controller/IPDisplayClusterNodeController.h" +#include "Config/IPDisplayClusterConfigManager.h" +#include "Input/IPDisplayClusterInputManager.h" + +#include "Misc/App.h" +#include "Misc/CommandLine.h" +#include "Misc/DisplayClusterAppExit.h" +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/DisplayClusterLog.h" +#include "Misc/Parse.h" +#include "DisplayClusterBuildConfig.h" +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +void UDisplayClusterGameEngine::Init(class IEngineLoop* InEngineLoop) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterEngine); + + // Detect requested operation mode + OperationMode = DetectOperationMode(); + + // Initialize Display Cluster + if (!GDisplayCluster->Init(OperationMode)) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::KillImmediately, FString("Couldn't initialize DisplayCluster module")); + } + + FString cfgPath; + FString nodeId; + + if (OperationMode == EDisplayClusterOperationMode::Cluster) + { + // Extract config path from command line + if (!FParse::Value(FCommandLine::Get(), DisplayClusterStrings::args::Config, cfgPath)) + { + UE_LOG(LogDisplayClusterEngine, Error, TEXT("No config file specified")); + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::KillImmediately, FString("Cluster mode requires config file")); + } + + // Extract node ID from command line + if (!FParse::Value(FCommandLine::Get(), DisplayClusterStrings::args::Node, nodeId)) + { +#ifdef DISPLAY_CLUSTER_USE_AUTOMATIC_NODE_ID_RESOLVE + UE_LOG(LogDisplayClusterEngine, Log, TEXT("Node ID is not specified")); +#else + UE_LOG(LogDisplayClusterEngine, Warning, TEXT("Node ID is not specified")); + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::KillImmediately, FString("Cluster mode requires node ID")); +#endif + } + } + else if (OperationMode == EDisplayClusterOperationMode::Standalone) + { +#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG + // Save config path from command line + cfgPath = DisplayClusterStrings::misc::DbgStubConfig; + nodeId = DisplayClusterStrings::misc::DbgStubNodeId; +#endif + } + + if (OperationMode == EDisplayClusterOperationMode::Cluster || + OperationMode == EDisplayClusterOperationMode::Standalone) + { + DisplayClusterHelpers::str::DustCommandLineValue(cfgPath); + DisplayClusterHelpers::str::DustCommandLineValue(nodeId); + + // Start game session + if (!GDisplayCluster->StartSession(cfgPath, nodeId)) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::KillImmediately, FString("Couldn't start DisplayCluster session")); + } + + // Initialize internals + InitializeInternals(); + } + + // Initialize base stuff. + UGameEngine::Init(InEngineLoop); +} + +EDisplayClusterOperationMode UDisplayClusterGameEngine::DetectOperationMode() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterEngine); + + EDisplayClusterOperationMode OpMode = EDisplayClusterOperationMode::Disabled; + if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::Cluster)) + { + OpMode = EDisplayClusterOperationMode::Cluster; + } + else if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::Standalone)) + { + OpMode = EDisplayClusterOperationMode::Standalone; + } + + UE_LOG(LogDisplayClusterEngine, Log, TEXT("Detected operation mode: %s"), *FDisplayClusterTypesConverter::ToString(OpMode)); + + return OpMode; +} + +bool UDisplayClusterGameEngine::InitializeInternals() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterEngine); + + // Store debug settings locally + CfgDebug = GDisplayCluster->GetPrivateConfigMgr()->GetConfigDebug(); + + InputMgr = GDisplayCluster->GetPrivateInputMgr(); + ClusterMgr = GDisplayCluster->GetPrivateClusterMgr(); + NodeController = ClusterMgr->GetController(); + + FDisplayClusterConfigClusterNode nodeCfg; + if (GDisplayCluster->GetPrivateConfigMgr()->GetLocalClusterNode(nodeCfg)) + { + UE_LOG(LogDisplayClusterEngine, Log, TEXT("Configuring sound enabled: %s"), *FDisplayClusterTypesConverter::ToString(nodeCfg.SoundEnabled)); + bUseSound = nodeCfg.SoundEnabled; + } + + check(ClusterMgr); + check(InputMgr); + + return true; +} + +void UDisplayClusterGameEngine::PreExit() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterEngine); + + if (OperationMode == EDisplayClusterOperationMode::Cluster || + OperationMode == EDisplayClusterOperationMode::Standalone) + { + // Close current DisplayCluster session + GDisplayCluster->EndSession(); + } + + // Release the engine + UGameEngine::PreExit(); +} + +bool UDisplayClusterGameEngine::LoadMap(FWorldContext& WorldContext, FURL URL, class UPendingNetGame* Pending, FString& Error) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterEngine); + + // Perform map loading + if (!Super::LoadMap(WorldContext, URL, Pending, Error)) + { + return false; + } + + if (OperationMode == EDisplayClusterOperationMode::Cluster || + OperationMode == EDisplayClusterOperationMode::Standalone) + { + // Game start barrier + NodeController->WaitForGameStart(); + } + + return true; +} + +void UDisplayClusterGameEngine::Tick(float DeltaSeconds, bool bIdleMode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterEngine); + + if (OperationMode == EDisplayClusterOperationMode::Cluster || + OperationMode == EDisplayClusterOperationMode::Standalone) + { + + // Update input device state (master only) + InputMgr->Update(); + + // Update delta time. Cluster slaves will get this value from the master few steps later + ClusterMgr->SetDeltaTime(DeltaSeconds); + + // Sync cluster objects + ClusterMgr->SyncObjects(); + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Frame start barrier + NodeController->WaitForFrameStart(); + UE_LOG(LogDisplayClusterEngine, Verbose, TEXT("Sync frame start")); + + // Get DisplayCluster time delta + NodeController->GetDeltaTime(DeltaSeconds); + UE_LOG(LogDisplayClusterEngine, Verbose, TEXT("DisplayCluster delta time (seconds): %f"), DeltaSeconds); + + // Update delta time in the application + FApp::SetDeltaTime(DeltaSeconds); + + // Update input state in the cluster + ClusterMgr->SyncInput(); + + // Perform PreTick for DisplayCluster module + UE_LOG(LogDisplayClusterEngine, Verbose, TEXT("Perform PreTick()")); + GDisplayCluster->PreTick(DeltaSeconds); + + // Perform Tick() calls for scene actors + UE_LOG(LogDisplayClusterEngine, Verbose, TEXT("Perform Tick()")); + Super::Tick(DeltaSeconds, bIdleMode); + + if (CfgDebug.LagSimulateEnabled) + { + const float lag = CfgDebug.LagMaxTime; + UE_LOG(LogDisplayClusterEngine, Log, TEXT("Simulating lag: %f seconds"), lag); + FPlatformProcess::Sleep(lag); + } + +#if 0 + ////////////////////////////////////////////////////////////////////////////////////////////// + // Tick end barrier + NodeController->WaitForTickEnd(); +#endif + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Frame end barrier + NodeController->WaitForFrameEnd(); + UE_LOG(LogDisplayClusterEngine, Verbose, TEXT("Sync frame end")); + } + else + { + Super::Tick(DeltaSeconds, bIdleMode); + } +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameMode.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameMode.cpp new file mode 100644 index 000000000000..4621f82c0de3 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameMode.cpp @@ -0,0 +1,212 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterGameMode.h" + +#include "Game/IPDisplayClusterGameManager.h" +#include "Input/IPDisplayClusterInputManager.h" + +#include "Misc/DisplayClusterAppExit.h" +#include "Misc/DisplayClusterLog.h" +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/Paths.h" + +#include "DisplayClusterPawn.h" +#include "DisplayClusterSettings.h" + +#include "DisplayClusterStrings.h" +#include "DisplayClusterPlayerController.h" +#include "DisplayClusterHUD.h" +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +#if WITH_EDITOR +bool ADisplayClusterGameMode::bNeedSessionStart = true; +bool ADisplayClusterGameMode::bSessionStarted = false; +#endif + + +ADisplayClusterGameMode::ADisplayClusterGameMode() : + Super() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (!bIsDisplayClusterActive) + { + return; + } + + DefaultPawnClass = ADisplayClusterPawn::StaticClass(); + PlayerControllerClass = ADisplayClusterPlayerController::StaticClass(); + HUDClass = ADisplayClusterHUD::StaticClass(); +} + +ADisplayClusterGameMode::~ADisplayClusterGameMode() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); +} + +void ADisplayClusterGameMode::InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + Super::InitGame(MapName, Options, ErrorMessage); + + UE_LOG(LogDisplayClusterGame, Log, TEXT("%s"), bIsDisplayClusterActive ? + TEXT("DisplayCluster feature is active for this world.") : + TEXT("DisplayCluster feature has been deactivated for this world by game mode.")); + + if (!bIsDisplayClusterActive) + { + return; + } + +#if WITH_EDITOR + if (GIsEditor && ADisplayClusterGameMode::bNeedSessionStart) + { + // Look for DisplayClusterSettings actor + TArray Settings; + DisplayClusterHelpers::game::FindAllActors(GetWorld(), Settings); + + FString NodeId; + FString ConfigPath; + + // Extract user settings + if (Settings.Num() > 0) + { + NodeId = Settings[0]->EditorNodeId; + ConfigPath = Settings[0]->EditorConfigPath; + } + else + { + UE_LOG(LogDisplayClusterGame, Warning, TEXT("No DisplayCluster settings found. Using defaults.")); + + NodeId = DisplayClusterStrings::misc::DbgStubNodeId; + ConfigPath = DisplayClusterStrings::misc::DbgStubConfig; + } + + DisplayClusterHelpers::str::DustCommandLineValue(ConfigPath); + DisplayClusterHelpers::str::DustCommandLineValue(NodeId); + + // Check if config path is relative. In this case we have to build an absolute path from a project directory. + if (FPaths::IsRelative(ConfigPath)) + { + UE_LOG(LogDisplayClusterGame, Log, TEXT("Relative path detected. Generating absolute path...")); + ConfigPath = FPaths::Combine(FPaths::ProjectDir(), ConfigPath); + ConfigPath = FPaths::ConvertRelativePathToFull(ConfigPath); + UE_LOG(LogDisplayClusterGame, Log, TEXT("Absolute path: %s"), *ConfigPath); + } + + ADisplayClusterGameMode::bSessionStarted = GDisplayCluster->StartSession(ConfigPath, NodeId); + if (!ADisplayClusterGameMode::bSessionStarted) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("Couldn't start DisplayCluster session")); + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Couldn't start DisplayCluster session")); + } + + // Subscribe to EndPIE event to close the DisplayCluster session + EndPIEDelegate = FEditorDelegates::EndPIE.AddUObject(this, &ADisplayClusterGameMode::OnEndPIE); + + // Don't start DisplayCluster session again after LoadLevel + ADisplayClusterGameMode::bNeedSessionStart = false; + } +#endif +} + +void ADisplayClusterGameMode::StartPlay() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (bIsDisplayClusterActive) + { + IPDisplayClusterGameManager* const pGameMgr = GDisplayCluster->GetPrivateGameMgr(); + if (pGameMgr) + { + // Set current DisplayClusterGameMode + pGameMgr->SetDisplayClusterGameMode(this); + + TArray Settings; + DisplayClusterHelpers::game::FindAllActors(GetWorld(), Settings); + + // Set current DisplayCluster scene settings + if (Settings.Num()) + { + UE_LOG(LogDisplayClusterGame, Log, TEXT("Found DisplayCluster settings for this level")); + pGameMgr->SetDisplayClusterSceneSettings(Settings[0]); + } + } + } + + Super::StartPlay(); +} + + +void ADisplayClusterGameMode::BeginPlay() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (bIsDisplayClusterActive) + { + bGameStarted = GDisplayCluster->StartScene(GetWorld()); + if (!bGameStarted) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("Couldn't start game")); + GetWorld()->Exec(GetWorld(), TEXT("quit")); + } + } + + Super::BeginPlay(); +} + +void ADisplayClusterGameMode::BeginDestroy() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (bIsDisplayClusterActive) + { + if (bGameStarted) + { + GDisplayCluster->EndScene(); + } + + // ... + } + + Super::BeginDestroy(); +} + +void ADisplayClusterGameMode::Tick(float DeltaSeconds) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + Super::Tick(DeltaSeconds); + + if (!bIsDisplayClusterActive) + { + return; + } + +#if WITH_EDITOR + IPDisplayClusterInputManager* const pInputMgr = GDisplayCluster->GetPrivateInputMgr(); + if (pInputMgr) + { + pInputMgr->Update(); + } + + GDisplayCluster->PreTick(DeltaSeconds); +#endif +} + +#if WITH_EDITOR +void ADisplayClusterGameMode::OnEndPIE(const bool bSimulate) +{ + if (GIsEditor) + { + FEditorDelegates::EndPIE.Remove(EndPIEDelegate); + GDisplayCluster->EndSession(); + + ADisplayClusterGameMode::bNeedSessionStart = true; + ADisplayClusterGameMode::bSessionStarted = false; + } +} +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameModeDefault.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameModeDefault.cpp new file mode 100644 index 000000000000..e74f1b27dbdd --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterGameModeDefault.cpp @@ -0,0 +1,26 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterGameModeDefault.h" +#include "DisplayClusterPawnDefault.h" + +#include "Misc/DisplayClusterLog.h" + + +ADisplayClusterGameModeDefault::ADisplayClusterGameModeDefault() : + Super() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (!bIsDisplayClusterActive) + { + return; + } + + DefaultPawnClass = ADisplayClusterPawnDefault::StaticClass(); +} + +ADisplayClusterGameModeDefault::~ADisplayClusterGameModeDefault() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterHUD.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterHUD.cpp new file mode 100644 index 000000000000..d2f63b55bebc --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterHUD.cpp @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterHUD.h" + + +ADisplayClusterHUD::ADisplayClusterHUD(const FObjectInitializer& ObjectInitializer) : + AHUD(ObjectInitializer) +{ + PrimaryActorTick.bCanEverTick = true; +} + + +void ADisplayClusterHUD::BeginPlay() +{ + Super::BeginPlay(); +} + +void ADisplayClusterHUD::DrawHUD() +{ + Super::DrawHUD(); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterPlayerController.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterPlayerController.cpp new file mode 100644 index 000000000000..c14a65c04a9f --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Basics/DisplayClusterPlayerController.cpp @@ -0,0 +1,33 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterPlayerController.h" +#include "Misc/DisplayClusterAppExit.h" + + +void ADisplayClusterPlayerController::PlayerTick(float DeltaTime) +{ + Super::PlayerTick(DeltaTime); + + if (WasInputKeyJustPressed(EKeys::Escape)) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Exit on ESC requested")); + } +} + +void ADisplayClusterPlayerController::BeginPlay() +{ + Super::BeginPlay(); + +#if 0 + //@todo: temporary solution. we need generic DisplayCluster access to statistics + //@note: next line causes crash + FDisplayClusterConfigDebug cfgDebug = FGDisplayCluster->GetPrivateConfigMgr()->GetConfigDebug(); + if (cfgDebug.DrawStats) + { + UE_LOG(LogDisplayClusterGame, Log, TEXT("Activating onscreen stats")); + ConsoleCommand(FString("stat fps"), true); + ConsoleCommand(FString("stat unit"), true); + } +#endif +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterCameraComponent.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterCameraComponent.cpp new file mode 100644 index 000000000000..1e367e1058cf --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterCameraComponent.cpp @@ -0,0 +1,36 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterCameraComponent.h" + + +UDisplayClusterCameraComponent::UDisplayClusterCameraComponent(const FObjectInitializer& ObjectInitializer) : + UDisplayClusterSceneComponent(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; +} + + +void UDisplayClusterCameraComponent::BeginPlay() +{ + Super::BeginPlay(); + + // ... + +} + +void UDisplayClusterCameraComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) +{ + Super::TickComponent( DeltaTime, TickType, ThisTickFunction ); + + // ... +} + +void UDisplayClusterCameraComponent::SetSettings(const FDisplayClusterConfigSceneNode* pConfig) +{ + Super::SetSettings(pConfig); +} + +bool UDisplayClusterCameraComponent::ApplySettings() +{ + return Super::ApplySettings(); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterPawn.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterPawn.cpp new file mode 100644 index 000000000000..1a09a0ed5c6d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterPawn.cpp @@ -0,0 +1,108 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterPawn.h" + +#include "Engine/CollisionProfile.h" + +#include "Camera/CameraComponent.h" +#include "Components/SphereComponent.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Game/IPDisplayClusterGameManager.h" + +#include "DisplayClusterSceneComponentSyncParent.h" + +#include "IPDisplayCluster.h" +#include "Misc/DisplayClusterLog.h" +#include "DisplayClusterSettings.h" +#include "DisplayClusterGameMode.h" +#include "DisplayClusterGlobals.h" + + +ADisplayClusterPawn::ADisplayClusterPawn(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + // Collision component + CollisionComponent = CreateDefaultSubobject(TEXT("CollisionComponent0")); + CollisionComponent->InitSphereRadius(35.0f); + CollisionComponent->SetCollisionProfileName(UCollisionProfile::Pawn_ProfileName); + CollisionComponent->CanCharacterStepUpOn = ECB_No; + CollisionComponent->SetCanEverAffectNavigation(true); + CollisionComponent->bDynamicObstacle = true; + CollisionComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + // Collision component must always be a root + RootComponent = CollisionComponent; + + // Collision offset component + CollisionOffsetComponent = CreateDefaultSubobject(TEXT("DisplayCluster_offset")); + CollisionOffsetComponent->AttachToComponent(RootComponent, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + + // DisplayCluster sync + DisplayClusterSyncRoot = CreateDefaultSubobject(TEXT("DisplayCluster_root_sync")); + DisplayClusterSyncRoot->AttachToComponent(RootComponent, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + + DisplayClusterSyncCollisionOffset = CreateDefaultSubobject(TEXT("DisplayCluster_colloffset_sync")); + DisplayClusterSyncCollisionOffset->AttachToComponent(CollisionOffsetComponent, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + + // Camera + CameraComponent = CreateDefaultSubobject(TEXT("DisplayCluster_camera")); + CameraComponent->AttachToComponent(CollisionOffsetComponent, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + CameraComponent->bUsePawnControlRotation = false; + CameraComponent->bAbsoluteLocation = false; + CameraComponent->bAbsoluteRotation = false; + + PrimaryActorTick.bCanEverTick = true; + bFindCameraComponentWhenViewTarget = true; + bCanBeDamaged = false; + bReplicates = false; + SpawnCollisionHandlingMethod = ESpawnActorCollisionHandlingMethod::AdjustIfPossibleButAlwaysSpawn; +} + +void ADisplayClusterPawn::BeginPlay() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + Super::BeginPlay(); + + GameMgr = GDisplayCluster->GetPrivateGameMgr(); + bIsCluster = (GDisplayCluster->GetOperationMode() == EDisplayClusterOperationMode::Cluster); + + // No collision by default + CollisionComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + + // Enable collision if needed + if (GameMgr->IsDisplayClusterActive()) + { + const ADisplayClusterSettings* const pDisplayClusterSettings = GameMgr->GetDisplayClusterSceneSettings(); + + if (GDisplayCluster->GetPrivateClusterMgr()->IsMaster()) + { + if (pDisplayClusterSettings && pDisplayClusterSettings->bEnableCollisions) + { + // Enable collisions + CollisionComponent->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + // Apply collision related offset to DisplayCluster hierarchy + const FVector CollisionOffset(0.f, 0.f, -CollisionComponent->GetUnscaledSphereRadius()); + CollisionOffsetComponent->SetRelativeLocation(CollisionOffset); + UE_LOG(LogDisplayClusterGame, Log, TEXT("Collision offset: %s"), *CollisionOffset.ToString()); + } + } + } +} + +void ADisplayClusterPawn::BeginDestroy() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + Super::BeginDestroy(); +} + +void ADisplayClusterPawn::Tick(float DeltaSeconds) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + Super::Tick(DeltaSeconds); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterPawnDefault.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterPawnDefault.cpp new file mode 100644 index 000000000000..a46c635c079b --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterPawnDefault.cpp @@ -0,0 +1,248 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterPawnDefault.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Game/IPDisplayClusterGameManager.h" + +#include "DisplayClusterSceneComponentSyncParent.h" + +#include "DisplayClusterSettings.h" +#include "DisplayClusterGameMode.h" +#include "DisplayClusterGlobals.h" + +#include "Engine/World.h" +#include "Misc/DisplayClusterLog.h" +#include "GameFramework/WorldSettings.h" + +#include "IPDisplayCluster.h" + + +ADisplayClusterPawnDefault::ADisplayClusterPawnDefault(const FObjectInitializer& ObjectInitializer) : + Super(ObjectInitializer) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + // Movement component + MovementComponent = CreateDefaultSubobject(TEXT("MovementComponent0")); + MovementComponent->UpdatedComponent = RootComponent; + + // Rotation component + RotatingComponent = CreateDefaultSubobject(TEXT("RotatingComponent0")); + RotatingComponent->UpdatedComponent = RootComponent; + RotatingComponent->bRotationInLocalSpace = true; + RotatingComponent->PivotTranslation = FVector::ZeroVector; + RotatingComponent->RotationRate = FRotator::ZeroRotator; + + // Rotation component2 + RotatingComponent2 = CreateDefaultSubobject(TEXT("RotatingComponent1")); + RotatingComponent2->UpdatedComponent = RootComponent; + RotatingComponent2->bRotationInLocalSpace = false; + RotatingComponent2->PivotTranslation = FVector::ZeroVector; + RotatingComponent2->RotationRate = FRotator::ZeroRotator; + + BaseTurnRate = 45.f; + BaseLookUpRate = 45.f; +} + +void ADisplayClusterPawnDefault::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + check(PlayerInputComponent); + + Super::SetupPlayerInputComponent(PlayerInputComponent); + + if (PlayerInputComponent) + { + PlayerInputComponent->BindAxis("MoveForward", this, &ADisplayClusterPawnDefault::MoveForward); + PlayerInputComponent->BindAxis("MoveRight", this, &ADisplayClusterPawnDefault::MoveRight); + PlayerInputComponent->BindAxis("MoveUp", this, &ADisplayClusterPawnDefault::MoveUp); + PlayerInputComponent->BindAxis("TurnRate", this, &ADisplayClusterPawnDefault::TurnAtRate2); + PlayerInputComponent->BindAxis("LookUpRate", this, &ADisplayClusterPawnDefault::LookUpAtRate); + } +} + +void ADisplayClusterPawnDefault::BeginPlay() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + Super::BeginPlay(); + + GameMgr = GDisplayCluster->GetPrivateGameMgr(); + bIsCluster = (GDisplayCluster->GetOperationMode() == EDisplayClusterOperationMode::Cluster); + + bUseControllerRotationYaw = !bIsCluster; + bUseControllerRotationPitch = !bIsCluster; + bUseControllerRotationRoll = !bIsCluster; + + // Enable collision if needed + if (GameMgr->IsDisplayClusterActive()) + { + const ADisplayClusterSettings* const pDisplayClusterSettings = GameMgr->GetDisplayClusterSceneSettings(); + if (pDisplayClusterSettings) + { + // Apply movement settings + MovementComponent->MaxSpeed = pDisplayClusterSettings->MovementMaxSpeed; + MovementComponent->Acceleration = pDisplayClusterSettings->MovementAcceleration; + MovementComponent->Deceleration = pDisplayClusterSettings->MovementDeceleration; + MovementComponent->TurningBoost = pDisplayClusterSettings->MovementTurningBoost; + + // Apply rotation settings + BaseTurnRate = pDisplayClusterSettings->RotationSpeed; + BaseLookUpRate = pDisplayClusterSettings->RotationSpeed; + } + } +} + +void ADisplayClusterPawnDefault::BeginDestroy() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + Super::BeginDestroy(); +} + +void ADisplayClusterPawnDefault::Tick(float DeltaSeconds) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + Super::Tick(DeltaSeconds); + + const float Mult = GetWorld()->GetWorldSettings()->WorldToMeters / 100.f; + SetActorScale3D(FVector(Mult, Mult, Mult)); +} + +void ADisplayClusterPawnDefault::MoveRight(float Val) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (Val != 0.f) + { + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("ADisplayClusterPawn::MoveRight: %f"), Val); + + const USceneComponent* const pComp = (TranslationDirection ? TranslationDirection : RootComponent); + AddMovementInput(pComp->GetRightVector(), Val); + } +} + +void ADisplayClusterPawnDefault::MoveForward(float Val) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (Val != 0.f) + { + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("ADisplayClusterPawn::MoveForward: %f"), Val); + + const USceneComponent* const pComp = (TranslationDirection ? TranslationDirection : RootComponent); + AddMovementInput(pComp->GetForwardVector(), Val); + } +} + +void ADisplayClusterPawnDefault::MoveUp(float Val) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (Val != 0.f) + { + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("ADisplayClusterPawn::MoveUp: %f"), Val); + + const USceneComponent* const pComp = (TranslationDirection ? TranslationDirection : RootComponent); + AddMovementInput(pComp->GetUpVector(), Val); + } +} + +void ADisplayClusterPawnDefault::TurnAtRate(float Rate) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("ADisplayClusterPawn::TurnAtRate: %f"), Rate); + + if (bIsCluster) + { + IPDisplayClusterGameManager* const pMgr = GDisplayCluster->GetPrivateGameMgr(); + if (pMgr) + { + auto* const pCam = pMgr->GetActiveCamera(); + if (pCam) + { + if (RotatingComponent->UpdatedComponent) + { + const FTransform TransformToRotate = RotatingComponent->UpdatedComponent->GetComponentTransform(); + const FVector RotateAroundPivot = TransformToRotate.InverseTransformPositionNoScale(pCam->GetComponentLocation()); + RotatingComponent->PivotTranslation = RotateAroundPivot; + RotatingComponent->RotationRate = FRotator(RotatingComponent->RotationRate.Pitch, Rate * BaseTurnRate, 0.f); + } + } + } + } + else + { + if (Rate != 0.f) + { + AddControllerYawInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation); + } + } +} + +void ADisplayClusterPawnDefault::TurnAtRate2(float Rate) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("ADisplayClusterPawn::TurnAtRate2: %f"), Rate); + + if (bIsCluster) + { + IPDisplayClusterGameManager* const pMgr = GDisplayCluster->GetPrivateGameMgr(); + if (pMgr) + { + UDisplayClusterCameraComponent* const pCam = pMgr->GetActiveCamera(); + if (pCam) + { + if (RotatingComponent2->UpdatedComponent) + { + const FTransform TransformToRotate = RotatingComponent2->UpdatedComponent->GetComponentTransform(); + const FVector RotateAroundPivot = TransformToRotate.InverseTransformPositionNoScale(pCam->GetComponentLocation()); + RotatingComponent2->PivotTranslation = RotateAroundPivot; + RotatingComponent2->RotationRate = FRotator(RotatingComponent2->RotationRate.Pitch, Rate * BaseTurnRate, 0.f); + } + } + } + } + else + { + if (Rate != 0.f) + { + AddControllerYawInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation); + } + } +} + +void ADisplayClusterPawnDefault::LookUpAtRate(float Rate) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (bIsCluster) + { +#if 0 + //@todo: rotate around active camera + IPDisplayClusterGameManager* const pMgr = GDisplayCluster->GetPrivateGameMgr(); + if (pMgr) + { + auto* const pCam = pMgr->GetActiveCamera(); + if (pCam) + { + RotatingComponent->bRotationInLocalSpace = true; + RotatingComponent->PivotTranslation = FVector::ZeroVector; + RotatingComponent->RotationRate = FRotator(Rate * BaseLookUpRate, RotatingComponent->RotationRate.Yaw, 0.f); + } + } +#endif + } + else + { + if (Rate != 0.f) + { + AddControllerPitchInput(BaseTurnRate * Rate * GetWorld()->GetDeltaSeconds() * CustomTimeDilation); + } + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponent.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponent.cpp new file mode 100644 index 000000000000..87da20adeed7 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponent.cpp @@ -0,0 +1,84 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSceneComponent.h" + +#include "Config/DisplayClusterConfigTypes.h" +#include "Game/IPDisplayClusterGameManager.h" +#include "Input/IPDisplayClusterInputManager.h" +#include "Misc/DisplayClusterLog.h" + +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +UDisplayClusterSceneComponent::UDisplayClusterSceneComponent(const FObjectInitializer& ObjectInitializer) : + USceneComponent(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; +} + +void UDisplayClusterSceneComponent::BeginPlay() +{ + Super::BeginPlay(); +} + +void UDisplayClusterSceneComponent::BeginDestroy() +{ + Super::BeginDestroy(); +} + +void UDisplayClusterSceneComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // Update transform if attached to a tracker + if (!Config.TrackerId.IsEmpty()) + { + IPDisplayClusterInputManager* const inputMgr = GDisplayCluster->GetPrivateInputMgr(); + if (inputMgr) + { + FVector loc; + FQuat rot; + const bool bLocAvail = inputMgr->GetTrackerLocation(Config.TrackerId, Config.TrackerCh, loc); + const bool bRotAvail = inputMgr->GetTrackerQuat(Config.TrackerId, Config.TrackerCh, rot); + + if (bLocAvail && bRotAvail) + { + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("%s[%s] update from tracker %s:%d - {loc %s} {quat %s}"), + *GetName(), *GetId(), *Config.TrackerId, Config.TrackerCh, *loc.ToString(), *rot.ToString()); + + // Update transform + this->SetRelativeLocationAndRotation(loc, rot); + // Force child transforms update + UpdateChildTransforms(/*true*/); + } + } + } +} + +void UDisplayClusterSceneComponent::SetSettings(const FDisplayClusterConfigSceneNode* pConfig) +{ + check(pConfig); + + Config = *pConfig; + + // Convert m to cm + Config.Loc *= 100.f; +} + +bool UDisplayClusterSceneComponent::ApplySettings() +{ + // Take place in hierarchy + if (!GetParentId().IsEmpty()) + { + UE_LOG(LogDisplayClusterGame, Log, TEXT("Attaching %s to %s"), *GetId(), *GetParentId()); + UDisplayClusterSceneComponent* const pComp = GDisplayCluster->GetPrivateGameMgr()->GetNodeById(GetParentId()); + AttachToComponent(pComp, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + //this->SetRelativeTransform(FTransform::Identity); + } + + // Set up location and rotation + this->SetRelativeLocationAndRotation(Config.Loc, Config.Rot); + + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSync.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSync.cpp new file mode 100644 index 000000000000..c06dd6707fe5 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSync.cpp @@ -0,0 +1,93 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSceneComponentSync.h" + +#include "IPDisplayCluster.h" +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Game/IPDisplayClusterGameManager.h" +#include "Misc/DisplayClusterLog.h" +#include "DisplayClusterGlobals.h" + + +UDisplayClusterSceneComponentSync::UDisplayClusterSceneComponentSync(const FObjectInitializer& ObjectInitializer) : + USceneComponent(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; +} + +void UDisplayClusterSceneComponentSync::BeginPlay() +{ + Super::BeginPlay(); + + GameMgr = GDisplayCluster->GetPrivateGameMgr(); + + // Generate unique sync id + SyncId = GetSyncId(); + + if (GameMgr->IsDisplayClusterActive()) + { + // Register sync object + ClusterMgr = GDisplayCluster->GetPrivateClusterMgr(); + if (ClusterMgr) + { + UE_LOG(LogDisplayClusterGame, Log, TEXT("Registering sync object %s..."), *SyncId); + ClusterMgr->RegisterSyncObject(this); + } + else + { + UE_LOG(LogDisplayClusterGame, Warning, TEXT("Couldn't register %s scene component sync. Looks like we're in non-DisplayCluster mode."), *SyncId); + } + } +} + + +void UDisplayClusterSceneComponentSync::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) +{ + Super::TickComponent( DeltaTime, TickType, ThisTickFunction ); + + // ... +} + +void UDisplayClusterSceneComponentSync::DestroyComponent(bool bPromoteChildren) +{ + if (ClusterMgr) + { + UE_LOG(LogDisplayClusterGame, Log, TEXT("Unregistering sync object %s..."), *SyncId); + ClusterMgr->UnregisterSyncObject(this); + } + + Super::DestroyComponent(bPromoteChildren); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterClusterSyncObject +////////////////////////////////////////////////////////////////////////////////////////////// +FString UDisplayClusterSceneComponentSync::GetSyncId() const +{ + return FString::Printf(TEXT("S_%s"), *GetOwner()->GetName()); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterStringSerializable +////////////////////////////////////////////////////////////////////////////////////////////// +FString UDisplayClusterSceneComponentSync::SerializeToString() const +{ + return GetSyncTransform().ToString(); +} + +bool UDisplayClusterSceneComponentSync::DeserializeFromString(const FString& data) +{ + FTransform t; + if (!t.InitFromString(data)) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("Unable to deserialize transform data")); + return false; + } + + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("%s: applying transform data <%s>"), *SyncId, *t.ToHumanReadableString()); + SetSyncTransform(t); + + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSyncParent.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSyncParent.cpp new file mode 100644 index 000000000000..162d1b520879 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSyncParent.cpp @@ -0,0 +1,70 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSceneComponentSyncParent.h" +#include "GameFramework/Actor.h" + + +UDisplayClusterSceneComponentSyncParent::UDisplayClusterSceneComponentSyncParent(const FObjectInitializer& ObjectInitializer) : + UDisplayClusterSceneComponentSync(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = false; +} + +void UDisplayClusterSceneComponentSyncParent::BeginPlay() +{ + Super::BeginPlay(); + + // ... +} + + +void UDisplayClusterSceneComponentSyncParent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) +{ + Super::TickComponent( DeltaTime, TickType, ThisTickFunction ); + + // ... +} + +void UDisplayClusterSceneComponentSyncParent::DestroyComponent(bool bPromoteChildren) +{ + Super::DestroyComponent(bPromoteChildren); +} + + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterClusterSyncObject +////////////////////////////////////////////////////////////////////////////////////////////// +FString UDisplayClusterSceneComponentSyncParent::GetSyncId() const +{ + return FString::Printf(TEXT("SP_%s.%s"), *GetOwner()->GetName(), *GetAttachParent()->GetName()); +} + + +bool UDisplayClusterSceneComponentSyncParent::IsDirty() const +{ + USceneComponent* const pParent = GetAttachParent(); + return (LastSyncLoc != pParent->RelativeLocation || LastSyncRot != pParent->RelativeRotation || LastSyncScale != pParent->RelativeScale3D); +} + +void UDisplayClusterSceneComponentSyncParent::ClearDirty() +{ + USceneComponent* const pParent = GetAttachParent(); + LastSyncLoc = pParent->RelativeLocation; + LastSyncRot = pParent->RelativeRotation; + LastSyncScale = pParent->RelativeScale3D; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// UDisplayClusterSceneComponentSync +////////////////////////////////////////////////////////////////////////////////////////////// +FTransform UDisplayClusterSceneComponentSyncParent::GetSyncTransform() const +{ + return GetAttachParent()->GetRelativeTransform(); +} + +void UDisplayClusterSceneComponentSyncParent::SetSyncTransform(const FTransform& t) +{ + GetAttachParent()->SetRelativeTransform(t); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSyncThis.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSyncThis.cpp new file mode 100644 index 000000000000..6160061c64c4 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSceneComponentSyncThis.cpp @@ -0,0 +1,67 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSceneComponentSyncThis.h" + +#include "GameFramework/Actor.h" + + +UDisplayClusterSceneComponentSyncThis::UDisplayClusterSceneComponentSyncThis(const FObjectInitializer& ObjectInitializer) : + UDisplayClusterSceneComponentSync(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = false; +} + +void UDisplayClusterSceneComponentSyncThis::BeginPlay() +{ + Super::BeginPlay(); + + // ... +} + + +void UDisplayClusterSceneComponentSyncThis::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) +{ + Super::TickComponent( DeltaTime, TickType, ThisTickFunction ); + + // ... +} + +void UDisplayClusterSceneComponentSyncThis::DestroyComponent(bool bPromoteChildren) +{ + Super::DestroyComponent(bPromoteChildren); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterClusterSyncObject +////////////////////////////////////////////////////////////////////////////////////////////// +FString UDisplayClusterSceneComponentSyncThis::GetSyncId() const +{ + return FString::Printf(TEXT("ST_%s"), *GetOwner()->GetName()); +} + +bool UDisplayClusterSceneComponentSyncThis::IsDirty() const +{ + return (LastSyncLoc != RelativeLocation || LastSyncRot != RelativeRotation || LastSyncScale != RelativeScale3D); +} + +void UDisplayClusterSceneComponentSyncThis::ClearDirty() +{ + LastSyncLoc = RelativeLocation; + LastSyncRot = RelativeRotation; + LastSyncScale = RelativeScale3D; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// UDisplayClusterSceneComponentSync +////////////////////////////////////////////////////////////////////////////////////////////// +FTransform UDisplayClusterSceneComponentSyncThis::GetSyncTransform() const +{ + return GetRelativeTransform(); +} + +void UDisplayClusterSceneComponentSyncThis::SetSyncTransform(const FTransform& t) +{ + SetRelativeTransform(t); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterScreenComponent.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterScreenComponent.cpp new file mode 100644 index 000000000000..78cc8cc6cedf --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterScreenComponent.cpp @@ -0,0 +1,88 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterScreenComponent.h" +#include "DisplayClusterSettings.h" +#include "Components/StaticMeshComponent.h" +#include "Engine/GameEngine.h" +#include "Engine/StaticMesh.h" +#include "Materials/MaterialInterface.h" +#include "Materials/Material.h" +#include "UObject/ConstructorHelpers.h" + +#include "Game/IPDisplayClusterGameManager.h" +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" +#include "EngineDefines.h" + + +UDisplayClusterScreenComponent::UDisplayClusterScreenComponent(const FObjectInitializer& ObjectInitializer) : + UDisplayClusterSceneComponent(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + +#if WITH_EDITOR + if (GEngine && GEngine->IsEditor()) + { + const ADisplayClusterSettings* const pDisplayClusterSettings = GDisplayCluster->GetPrivateGameMgr()->GetDisplayClusterSceneSettings(); + if(pDisplayClusterSettings && pDisplayClusterSettings->bEditorShowProjectionScreens) + { + ScreenGeometryComponent = CreateDefaultSubobject(FName(*(GetName() + FString("_impl")))); + check(ScreenGeometryComponent); + + if (ScreenGeometryComponent) + { + static ConstructorHelpers::FObjectFinder screenMesh(TEXT("StaticMesh'/Engine/BasicShapes/Cube.Cube'")); + static ConstructorHelpers::FObjectFinder screenMat(TEXT("Material'/Engine/Engine_MI_Shaders/M_Shader_SimpleTranslucent.M_Shader_SimpleTranslucent'")); + + ScreenGeometryComponent->AttachToComponent(this, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + ScreenGeometryComponent->SetStaticMesh(screenMesh.Object); + ScreenGeometryComponent->SetMobility(EComponentMobility::Movable); + ScreenGeometryComponent->SetMaterial(0, screenMat.Object); + ScreenGeometryComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + } + } + } +#endif +} + + +void UDisplayClusterScreenComponent::BeginPlay() +{ + Super::BeginPlay(); + + // ... +} + + +void UDisplayClusterScreenComponent::TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + // ... +} + +void UDisplayClusterScreenComponent::SetSettings(const FDisplayClusterConfigSceneNode* pConfig) +{ + const FDisplayClusterConfigScreen* pScreenCfg = static_cast(pConfig); + Size = pScreenCfg->Size; + + Super::SetSettings(pConfig); +} + +bool UDisplayClusterScreenComponent::ApplySettings() +{ + Super::ApplySettings(); + +#if WITH_EDITOR + if (ScreenGeometryComponent) + { + ScreenGeometryComponent->RegisterComponent(); + ScreenGeometryComponent->AttachToComponent(this, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + ScreenGeometryComponent->SetRelativeLocationAndRotation(FVector::ZeroVector, FRotator::ZeroRotator, false); + } +#endif + + SetRelativeScale3D(FVector(0.0001f, Size.X, Size.Y)); + + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSettings.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSettings.cpp new file mode 100644 index 000000000000..8811141e7a4f --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/Classes/Scene/DisplayClusterSettings.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSettings.h" + + +ADisplayClusterSettings::ADisplayClusterSettings(const FObjectInitializer& ObjectInitializer) : + AActor(ObjectInitializer), + MovementMaxSpeed(1200.f), + MovementAcceleration(4000.f), + MovementDeceleration(8000.f), + MovementTurningBoost(8.f), + RotationSpeed(45.f) +{ + PrimaryActorTick.bCanEverTick = true; + +} + +ADisplayClusterSettings::~ADisplayClusterSettings() +{ +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/DisplayClusterGameManager.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/DisplayClusterGameManager.cpp new file mode 100644 index 000000000000..c1614857994f --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/DisplayClusterGameManager.cpp @@ -0,0 +1,491 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterGameManager.h" + +#include "Config/IPDisplayClusterConfigManager.h" + +#include "DisplayClusterGameMode.h" +#include "DisplayClusterSettings.h" + +#include "Kismet/GameplayStatics.h" +#include "Misc/CommandLine.h" +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/DisplayClusterLog.h" +#include "Config/DisplayClusterConfigTypes.h" + +#include "Camera/CameraComponent.h" +#include "Components/SceneComponent.h" +#include "DisplayClusterCameraComponent.h" +#include "DisplayClusterSceneComponent.h" + +#include "IPDisplayCluster.h" +#include "DisplayClusterGlobals.h" +#include "DisplayClusterStrings.h" + + +FDisplayClusterGameManager::FDisplayClusterGameManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); +} + +FDisplayClusterGameManager::~FDisplayClusterGameManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterGameManager::Init(EDisplayClusterOperationMode OperationMode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + CurrentOperationMode = OperationMode; + + return true; +} + +void FDisplayClusterGameManager::Release() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); +} + +bool FDisplayClusterGameManager::StartSession(const FString& configPath, const FString& nodeId) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + ConfigPath = configPath; + ClusterNodeId = nodeId; + + return true; +} + +void FDisplayClusterGameManager::EndSession() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); +} + +bool FDisplayClusterGameManager::StartScene(UWorld* pWorld) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + check(pWorld); + CurrentWorld = pWorld; + + VRRootActor = nullptr; + ActiveScreenComponent = nullptr; + ActiveCameraComponent = nullptr; + + // Clean containers. We store only pointers so there is no need to do any additional + // operations. All components will be destroyed by the engine. + ScreenComponents.Reset(); + CameraComponents.Reset(); + SceneNodeComponents.Reset(); + + if (IsDisplayClusterActive()) + { + //@todo: move initialization to DisplayClusterRoot side + if (!InitializeDisplayClusterActor()) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("Couldn't initialize DisplayCluster hierarchy")); + return false; + } + } + + return true; +} + +void FDisplayClusterGameManager::EndScene() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + FScopeLock lock(&InternalsSyncScope); + + VRRootActor = nullptr; + ActiveScreenComponent = nullptr; + ActiveCameraComponent = nullptr; + + // Clean containers. We store only pointers so there is no need to do any additional + // operations. All components will be destroyed by the engine. + ScreenComponents.Reset(); + CameraComponents.Reset(); + SceneNodeComponents.Reset(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterGameManager +////////////////////////////////////////////////////////////////////////////////////////////// +ADisplayClusterPawn* FDisplayClusterGameManager::GetRoot() const +{ + FScopeLock lock(&InternalsSyncScope); + return VRRootActor; +} + +TArray FDisplayClusterGameManager::GetAllScreens() const +{ + FScopeLock lock(&InternalsSyncScope); + return GetMapValues(ScreenComponents); +} + +UDisplayClusterScreenComponent* FDisplayClusterGameManager::GetActiveScreen() const +{ + FScopeLock lock(&InternalsSyncScope); + return ActiveScreenComponent; +} + +UDisplayClusterScreenComponent* FDisplayClusterGameManager::GetScreenById(const FString& id) const +{ + FScopeLock lock(&InternalsSyncScope); + return GetItem(ScreenComponents, id, FString("GetScreenById")); +} + +int32 FDisplayClusterGameManager::GetScreensAmount() const +{ + FScopeLock lock(&InternalsSyncScope); + return ScreenComponents.Num(); +} + +UDisplayClusterCameraComponent* FDisplayClusterGameManager::GetActiveCamera() const +{ + FScopeLock lock(&InternalsSyncScope); + return ActiveCameraComponent; +} + +UDisplayClusterCameraComponent* FDisplayClusterGameManager::GetCameraById(const FString& id) const +{ + FScopeLock lock(&InternalsSyncScope); + return GetItem(CameraComponents, id, FString("GetCameraById")); +} + +TArray FDisplayClusterGameManager::GetAllCameras() const +{ + FScopeLock lock(&InternalsSyncScope); + return GetMapValues(CameraComponents); +} + +int32 FDisplayClusterGameManager::GetCamerasAmount() const +{ + FScopeLock lock(&InternalsSyncScope); + return CameraComponents.Num(); +} + +void FDisplayClusterGameManager::SetActiveCamera(int32 idx) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (!IsDisplayClusterActive()) + { + return; + } + + check(idx < GDisplayCluster->GetPrivateConfigMgr()->GetCamerasAmount()); + + FDisplayClusterConfigCamera cam; + if (!GDisplayCluster->GetPrivateConfigMgr()->GetCamera(idx, cam)) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("Camera not found (idx=%d)"), idx); + return; + } + + return SetActiveCamera(cam.Id); +} + +void FDisplayClusterGameManager::SetActiveCamera(const FString& id) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (!IsDisplayClusterActive()) + { + return; + } + + FScopeLock lock(&InternalsSyncScope); + + if (!CameraComponents.Contains(id)) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("Couldn't switch camera. No such node id: %s"), *id); + return; + } + + ActiveCameraComponent = CameraComponents[id]; + VRRootActor->GetCameraComponent()->AttachToComponent(ActiveCameraComponent, FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + VRRootActor->GetCameraComponent()->SetRelativeLocation(FVector::ZeroVector); + VRRootActor->GetCameraComponent()->SetRelativeRotation(FRotator::ZeroRotator); + + // Update 'rotate around' component + SetRotateAroundComponent(ActiveCameraComponent); + + UE_LOG(LogDisplayClusterGame, Log, TEXT("Camera %s activated"), *ActiveCameraComponent->GetId()); +} + +UDisplayClusterSceneComponent* FDisplayClusterGameManager::GetNodeById(const FString& id) const +{ + FScopeLock lock(&InternalsSyncScope); + return GetItem(SceneNodeComponents, id, FString("GetNodeById")); +} + +TArray FDisplayClusterGameManager::GetAllNodes() const +{ + FScopeLock lock(&InternalsSyncScope); + return GetMapValues(SceneNodeComponents); +} + +USceneComponent* FDisplayClusterGameManager::GetTranslationDirectionComponent() const +{ + if (!IsDisplayClusterActive()) + return nullptr; + + FScopeLock lock(&InternalsSyncScope); + check(VRRootActor); + + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("GetTranslationDirectionComponent: %s"), (VRRootActor->TranslationDirection ? *VRRootActor->TranslationDirection->GetName() : TEXT("nullptr"))); + return VRRootActor->TranslationDirection; +} + +void FDisplayClusterGameManager::SetTranslationDirectionComponent(USceneComponent* pComp) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (!IsDisplayClusterActive()) + { + return; + } + + FScopeLock lock(&InternalsSyncScope); + check(VRRootActor); + + UE_LOG(LogDisplayClusterGame, Log, TEXT("New translation direction component set: %s"), (pComp ? *pComp->GetName() : TEXT("nullptr"))); + VRRootActor->TranslationDirection = pComp; +} + +void FDisplayClusterGameManager::SetTranslationDirectionComponent(const FString& id) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (!IsDisplayClusterActive()) + { + return; + } + + FScopeLock lock(&InternalsSyncScope); + check(VRRootActor); + + UE_LOG(LogDisplayClusterGame, Log, TEXT("New translation direction node id requested: %s"), *id); + SetTranslationDirectionComponent(GetNodeById(id)); +} + +USceneComponent* FDisplayClusterGameManager::GetRotateAroundComponent() const +{ + if (!IsDisplayClusterActive()) + { + return nullptr; + } + + FScopeLock lock(&InternalsSyncScope); + check(VRRootActor); + + UE_LOG(LogDisplayClusterGame, Verbose, TEXT("GetRotateAroundComponent: %s"), (VRRootActor->RotationAround ? *VRRootActor->RotationAround->GetName() : TEXT("nullptr"))); + return VRRootActor->RotationAround; +} + +void FDisplayClusterGameManager::SetRotateAroundComponent(USceneComponent* pComp) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (!IsDisplayClusterActive()) + { + return; + } + + FScopeLock lock(&InternalsSyncScope); + check(VRRootActor); + + UE_LOG(LogDisplayClusterGame, Log, TEXT("New rotate around component set: %s"), (pComp ? *pComp->GetName() : TEXT("nullptr"))); + VRRootActor->RotationAround = pComp; +} + +void FDisplayClusterGameManager::SetRotateAroundComponent(const FString& id) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + if (!IsDisplayClusterActive()) + { + return; + } + + FScopeLock lock(&InternalsSyncScope); + check(VRRootActor); + + UE_LOG(LogDisplayClusterGame, Log, TEXT("New rotate around node id requested: %s"), *id); + VRRootActor->RotationAround = GetNodeById(id); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterGameManager +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterGameManager::InitializeDisplayClusterActor() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterGame); + + APlayerController* pController = UGameplayStatics::GetPlayerController(CurrentWorld, 0); + check(pController); + + VRRootActor = StaticCast(pController->GetPawn()); + if (!VRRootActor) + { + // Seems the DisplayCluster features has been disabled + UE_LOG(LogDisplayClusterGame, Warning, TEXT("No DisplayCluster root found")); + return false; + } + + if (!(CreateCameras() && CreateScreens() && CreateNodes())) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("An error occurred during DisplayCluster root initialization")); + return false; + } + + // Let DisplayCluster nodes initialize ourselves + for (auto it = SceneNodeComponents.CreateIterator(); it; ++it) + { + if (it->Value->ApplySettings() == false) + { + UE_LOG(LogDisplayClusterGame, Warning, TEXT("Coulnd't initialize DisplayCluster node: ID=%s"), *it->Key); + } + } + + // Set the first camera active by default + SetActiveCamera(ActiveCameraComponent->GetId()); + + // Check if default camera was specified in command line arguments + FString camId; + if (FParse::Value(FCommandLine::Get(), DisplayClusterStrings::args::Camera, camId)) + { + DisplayClusterHelpers::str::DustCommandLineValue(camId); + UE_LOG(LogDisplayClusterGame, Log, TEXT("Default camera from command line arguments: %s"), *camId); + if (CameraComponents.Contains(camId)) + { + SetActiveCamera(camId); + } + } + + return true; +} + +bool FDisplayClusterGameManager::CreateScreens() +{ + // Get local screen settings + FDisplayClusterConfigScreen localScreen; + if (GDisplayCluster->GetPrivateConfigMgr()->GetLocalScreen(localScreen) == false) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("Couldn't get projection screen settings")); + return false; + } + + // Create screens + const TArray screens = GDisplayCluster->GetPrivateConfigMgr()->GetScreens(); + for (const auto& screen : screens) + { + // Create screen + UDisplayClusterScreenComponent* pScreen = NewObject(VRRootActor, FName(*screen.Id), RF_Transient); + check(pScreen); + + pScreen->AttachToComponent(VRRootActor->GetCollisionOffsetComponent(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + pScreen->RegisterComponent(); + + // Pass settings + pScreen->SetSettings(&screen); + + // Is this active screen (for this node)? + if (screen.Id == localScreen.Id) + ActiveScreenComponent = pScreen; + + // Store the screen + ScreenComponents.Add(screen.Id, pScreen); + SceneNodeComponents.Add(screen.Id, pScreen); + } + + // Check if local screen was found + check(ActiveScreenComponent); + if (!ActiveScreenComponent) + { + UE_LOG(LogDisplayClusterGame, Error, TEXT("Local screen not found")); + return false; + } + + return true; +} + +bool FDisplayClusterGameManager::CreateNodes() +{ + // Create other nodes + const TArray nodes = GDisplayCluster->GetPrivateConfigMgr()->GetSceneNodes(); + for (const auto& node : nodes) + { + UDisplayClusterSceneComponent* pNode = NewObject(VRRootActor, FName(*node.Id), RF_Transient); + check(pNode); + + pNode->AttachToComponent(VRRootActor->GetCollisionOffsetComponent(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + pNode->RegisterComponent(); + + pNode->SetSettings(&node); + SceneNodeComponents.Add(node.Id, pNode); + } + + return true; +} + +bool FDisplayClusterGameManager::CreateCameras() +{ + const TArray cams = GDisplayCluster->GetPrivateConfigMgr()->GetCameras(); + for (const auto& cam : cams) + { + UDisplayClusterCameraComponent* pCam = NewObject(VRRootActor, FName(*cam.Id), RF_Transient); + check(pCam); + + pCam->AttachToComponent(VRRootActor->GetCollisionOffsetComponent(), FAttachmentTransformRules(EAttachmentRule::KeepRelative, false)); + pCam->RegisterComponent(); + + pCam->SetSettings(&cam); + + CameraComponents.Add(cam.Id, pCam); + SceneNodeComponents.Add(cam.Id, pCam); + + if (ActiveCameraComponent == nullptr) + { + ActiveCameraComponent = pCam; + } + } + + // At least one camera must be set up + if (!ActiveCameraComponent) + { + UE_LOG(LogDisplayClusterGame, Warning, TEXT("No camera found")); + return false; + } + + return CameraComponents.Num() > 0; +} + +// Extracts array of values from a map +template +TArray FDisplayClusterGameManager::GetMapValues(const TMap& container) const +{ + TArray items; + container.GenerateValueArray(items); + return items; +} + +// Gets item by id. Performs checks and logging. +template +DataType* FDisplayClusterGameManager::GetItem(const TMap& container, const FString& id, const FString& logHeader) const +{ + if (container.Contains(id)) + { + return container[id]; + } + + UE_LOG(LogDisplayClusterGame, Warning, TEXT("%s: ID not found <%s>. Return nullptr."), *logHeader, *id); + return nullptr; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/DisplayClusterGameManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/DisplayClusterGameManager.h new file mode 100644 index 000000000000..f2bea767a78a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/DisplayClusterGameManager.h @@ -0,0 +1,127 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "IPDisplayClusterGameManager.h" +#include "DisplayClusterOperationMode.h" + +#include "DisplayClusterGameMode.h" +#include "DisplayClusterPawn.h" +#include "DisplayClusterSettings.h" +#include "DisplayClusterScreenComponent.h" +#include "DisplayClusterCameraComponent.h" + + +/** + * Game manager. Responsible for building VR object hierarchy from a config file. Implements some in-game logic. + */ +class FDisplayClusterGameManager + : public IPDisplayClusterGameManager +{ +public: + FDisplayClusterGameManager(); + virtual ~FDisplayClusterGameManager(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Init(EDisplayClusterOperationMode OperationMode) override; + virtual void Release() override; + virtual bool StartSession(const FString& configPath, const FString& nodeId) override; + virtual void EndSession() override; + virtual bool StartScene(UWorld* pWorld) override; + virtual void EndScene() override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterGameManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual ADisplayClusterPawn* GetRoot() const override; + + virtual TArray GetAllScreens() const override; + virtual UDisplayClusterScreenComponent* GetActiveScreen() const override; + virtual UDisplayClusterScreenComponent* GetScreenById(const FString& id) const override; + virtual int32 GetScreensAmount() const override; + + virtual TArray GetAllCameras() const override; + virtual UDisplayClusterCameraComponent* GetActiveCamera() const override; + virtual UDisplayClusterCameraComponent* GetCameraById(const FString& id) const override; + virtual int32 GetCamerasAmount() const override; + virtual void SetActiveCamera(int32 idx) override; + virtual void SetActiveCamera(const FString& id) override; + + virtual TArray GetAllNodes() const override; + virtual UDisplayClusterSceneComponent* GetNodeById(const FString& id) const override; + + virtual USceneComponent* GetTranslationDirectionComponent() const override; + virtual void SetTranslationDirectionComponent(USceneComponent* pComp) override; + virtual void SetTranslationDirectionComponent(const FString& id) override; + + virtual USceneComponent* GetRotateAroundComponent() const override; + virtual void SetRotateAroundComponent(USceneComponent* pComp) override; + virtual void SetRotateAroundComponent(const FString& id) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterGameManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool IsDisplayClusterActive() const override + { return ((CurrentOperationMode != EDisplayClusterOperationMode::Disabled) && (CurrentGameMode ? CurrentGameMode->IsDisplayClusterActive() : false)); } + + virtual void SetDisplayClusterGameMode(ADisplayClusterGameMode* pGameMode) override + { CurrentGameMode = pGameMode; } + + virtual ADisplayClusterGameMode* GetDisplayClusterGameMode() const override + { return CurrentGameMode; } + + virtual void SetDisplayClusterSceneSettings(ADisplayClusterSettings* pSceneSettings) override + { CurrentSceneSettings = pSceneSettings; } + + virtual ADisplayClusterSettings* GetDisplayClusterSceneSettings() const override + { return CurrentSceneSettings; } + +private: + // Creates DisplayCluster actor and fulfills with components hierarchy + bool InitializeDisplayClusterActor(); + + bool CreateScreens(); + bool CreateNodes(); + bool CreateCameras(); + + // Extracts array of values from a map + template + TArray GetMapValues(const TMap& container) const; + + // Gets item by id. Performs checks and logging. + template + DataType* GetItem(const TMap& container, const FString& id, const FString& logHeader) const; + +private: + // DisplayCluster root actor + ADisplayClusterPawn* VRRootActor = nullptr; + // Currently active projection screen (for this cluster node) + UDisplayClusterScreenComponent* ActiveScreenComponent = nullptr; + // Currently active camera (joint component) + UDisplayClusterCameraComponent* ActiveCameraComponent = nullptr; + + // Available screens (from config file) + TMap ScreenComponents; + // Available cameras (from config file) + TMap CameraComponents; + // All available DisplayCluster nodes in hierarchy + TMap SceneNodeComponents; + + EDisplayClusterOperationMode CurrentOperationMode; + FString ConfigPath; + FString ClusterNodeId; + UWorld* CurrentWorld; + + ADisplayClusterSettings* CurrentSceneSettings = nullptr; + ADisplayClusterGameMode* CurrentGameMode = nullptr; + + mutable FCriticalSection InternalsSyncScope; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/IPDisplayClusterGameManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/IPDisplayClusterGameManager.h new file mode 100644 index 000000000000..d0db9355e64b --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Game/IPDisplayClusterGameManager.h @@ -0,0 +1,29 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Game/IDisplayClusterGameManager.h" +#include "IPDisplayClusterManager.h" + +class ADisplayClusterGameMode; +class ADisplayClusterSettings; + + +/** + * Game manager private interface + */ +struct IPDisplayClusterGameManager : + public IDisplayClusterGameManager, + public IPDisplayClusterManager +{ + virtual ~IPDisplayClusterGameManager() + { } + + virtual bool IsDisplayClusterActive() const = 0; + + virtual void SetDisplayClusterGameMode(ADisplayClusterGameMode* pGameMode) = 0; + virtual ADisplayClusterGameMode* GetDisplayClusterGameMode() const = 0; + + virtual void SetDisplayClusterSceneSettings(ADisplayClusterSettings* pSceneSettings) = 0; + virtual ADisplayClusterSettings* GetDisplayClusterSceneSettings() const = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/IPDisplayCluster.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/IPDisplayCluster.h new file mode 100644 index 000000000000..3e40dbe6d286 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/IPDisplayCluster.h @@ -0,0 +1,35 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDisplayCluster.h" +#include "IPDisplayClusterManager.h" + +struct IPDisplayClusterRenderManager; +struct IPDisplayClusterClusterManager; +struct IPDisplayClusterInputManager; +struct IPDisplayClusterConfigManager; +struct IPDisplayClusterGameManager; + +class ADisplayClusterGameMode; +class ADisplayClusterSettings; + + +/** + * Private module interface + */ +struct IPDisplayCluster + : public IDisplayCluster + , public IPDisplayClusterManager +{ + virtual ~IPDisplayCluster() = 0 + { } + + virtual IPDisplayClusterRenderManager* GetPrivateRenderMgr() const = 0; + virtual IPDisplayClusterClusterManager* GetPrivateClusterMgr() const = 0; + virtual IPDisplayClusterInputManager* GetPrivateInputMgr() const = 0; + virtual IPDisplayClusterConfigManager* GetPrivateConfigMgr() const = 0; + virtual IPDisplayClusterGameManager* GetPrivateGameMgr() const = 0; + + virtual EDisplayClusterOperationMode GetOperationMode() const = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/IPDisplayClusterManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/IPDisplayClusterManager.h new file mode 100644 index 000000000000..932d3c71c196 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/IPDisplayClusterManager.h @@ -0,0 +1,48 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterOperationMode.h" + + +class ADisplayClusterGameMode; +class ADisplayClusterSettings; + + +/** + * Private manager interface + */ +struct IPDisplayClusterManager +{ + virtual ~IPDisplayClusterManager() = 0 + { } + + // Called at start to initialize internals + virtual bool Init(EDisplayClusterOperationMode OperationMode) + { return true; } + + // Called before application/Editor exit to release internals + virtual void Release() + { } + + // Called on each session start before first level start (before the first tick) + virtual bool StartSession(const FString& configPath, const FString& nodeId) + { return true; } + + // Called on each session end at early step before exit (before UGameEngine::Preexit) + virtual void EndSession() + { } + + // Called each time a new game level starts + virtual bool StartScene(UWorld* pWorld) + { return true; } + + // Called when current level is going to be closed (i.e. when loading new map) + virtual void EndScene() + { } + + // Called every frame before world Tick + virtual void PreTick(float DeltaSeconds) + { } +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/DisplayClusterInputDeviceBase.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/DisplayClusterInputDeviceBase.h new file mode 100644 index 000000000000..31f5c872f367 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/DisplayClusterInputDeviceBase.h @@ -0,0 +1,83 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDisplayClusterInputDevice.h" +#include "DisplayClusterInputDeviceTraits.h" + +#include "CoreMinimal.h" + + +/** + * Abstract input device + */ +template +class FDisplayClusterInputDeviceBase + : public IDisplayClusterInputDevice +{ +public: + typedef typename display_cluster_input_device_traits::dev_channel_data_type TChannelData; + +public: + FDisplayClusterInputDeviceBase(const FDisplayClusterConfigInput& config) : + ConfigData(config) + { } + + virtual ~FDisplayClusterInputDeviceBase() + { } + +public: + virtual bool GetChannelData(const uint8 channel, TChannelData& data) const + { + uint8 channelToGet = channel; + if (ConfigData.ChMap.Contains(channel)) + { + channelToGet = (uint8)ConfigData.ChMap[channel]; + UE_LOG(LogDisplayClusterInputVRPN, Verbose, TEXT("DevType %d, channel %d - remapped to channel %d"), DevTypeID, channel, channelToGet); + } + + if (!DeviceData.Contains(static_cast(channelToGet))) + { + UE_LOG(LogDisplayClusterInputVRPN, Verbose, TEXT("%s - channel %d data is not available yet"), *GetId(), channelToGet); + return false; + } + + data = DeviceData[static_cast(channelToGet)]; + + return true; + } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterInputDevice + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FString GetId() const override + { return ConfigData.Id; } + + virtual FString GetType() const override + { return ConfigData.Type; } + + virtual EDisplayClusterInputDevice GetTypeId() const override + { return static_cast(DevTypeID); } + + virtual FDisplayClusterConfigInput GetConfig() const override + { return ConfigData; } + + virtual void PreUpdate() override + { } + + virtual void Update() override + { } + + virtual void PostUpdate() override + { } + + virtual FString ToString() const override + { return FString::Printf(TEXT("DisplayCluster input device: id=%s, type=%s"), *GetId(), *GetType()); } + +protected: + // Original config data + const FDisplayClusterConfigInput ConfigData; + // Device data + TMap DeviceData; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/DisplayClusterInputDeviceTraits.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/DisplayClusterInputDeviceTraits.h new file mode 100644 index 000000000000..2b908d8fca6f --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/DisplayClusterInputDeviceTraits.h @@ -0,0 +1,52 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputData.h" +#include "Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputData.h" +#include "Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputData.h" + + +/** + * Available types of input devices + */ +enum EDisplayClusterInputDevice +{ + VrpnAnalog = 0, + VrpnButton, + VrpnTracker +}; + + +/** + * Input device traits + */ +template +struct display_cluster_input_device_traits { }; + +/** + * Specialization for VRPN analog device + */ +template <> +struct display_cluster_input_device_traits +{ + typedef FDisplayClusterVrpnAnalogChannelData dev_channel_data_type; +}; + +/** + * Specialization for VRPN button device + */ +template <> +struct display_cluster_input_device_traits +{ + typedef FDisplayClusterVrpnButtonChannelData dev_channel_data_type; +}; + +/** + * Specialization for VRPN tracker device + */ +template <> +struct display_cluster_input_device_traits +{ + typedef FDisplayClusterVrpnTrackerChannelData dev_channel_data_type; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/IDisplayClusterInputDevice.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/IDisplayClusterInputDevice.h new file mode 100644 index 000000000000..502f92f9d535 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/IDisplayClusterInputDevice.h @@ -0,0 +1,32 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterInputDeviceTraits.h" +#include "IDisplayClusterStringSerializable.h" + +#include "Config/DisplayClusterConfigTypes.h" + + +/** + * Interface for input devices + */ +struct IDisplayClusterInputDevice + : public IDisplayClusterStringSerializable +{ + virtual ~IDisplayClusterInputDevice() = 0 + { } + + virtual FString GetId() const = 0; + virtual FString GetType() const = 0; + virtual EDisplayClusterInputDevice GetTypeId() const = 0; + virtual FDisplayClusterConfigInput GetConfig() const = 0; + + virtual bool Initialize() = 0; + virtual void PreUpdate() = 0; + virtual void Update() = 0; + virtual void PostUpdate() = 0; + + virtual FString ToString() const = 0; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputData.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputData.h new file mode 100644 index 000000000000..b8da4c1239cc --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputData.h @@ -0,0 +1,13 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +/** + * VRPN analog device data type + */ +struct FDisplayClusterVrpnAnalogChannelData +{ + float axisValue; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDataHolder.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDataHolder.cpp new file mode 100644 index 000000000000..a6e465ca002c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDataHolder.cpp @@ -0,0 +1,61 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterVrpnAnalogInputDataHolder.h" +#include "Misc/DisplayClusterLog.h" + + +FDisplayClusterVrpnAnalogInputDataHolder::FDisplayClusterVrpnAnalogInputDataHolder(const FDisplayClusterConfigInput& config) : + FDisplayClusterInputDeviceBase(config) +{ +} + +FDisplayClusterVrpnAnalogInputDataHolder::~FDisplayClusterVrpnAnalogInputDataHolder() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterVrpnAnalogInputDataHolder::Initialize() +{ + return true; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterStringSerializable +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterVrpnAnalogInputDataHolder::SerializeToString() const +{ + FString result; + result.Reserve(128); + + for (auto it = DeviceData.CreateConstIterator(); it; ++it) + { + result += FString::Printf(TEXT("%d%s%f%s"), it->Key, SerializationDelimiter, it->Value.axisValue, SerializationDelimiter); + } + + return result; +} + +bool FDisplayClusterVrpnAnalogInputDataHolder::DeserializeFromString(const FString& data) +{ + TArray parsed; + data.ParseIntoArray(parsed, SerializationDelimiter); + + if (parsed.Num() % SerializationItems) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("Wrong items amount after deserialization [%s]"), *data); + return false; + } + + for (int i = 0; i < parsed.Num(); i += SerializationItems) + { + const int ch = FCString::Atoi(*parsed[i]); + const float val = FCString::Atof(*parsed[i + 1]); + DeviceData.Add(ch, FDisplayClusterVrpnAnalogChannelData{ val }); + } + + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDataHolder.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDataHolder.h new file mode 100644 index 000000000000..f1097a93c12d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDataHolder.h @@ -0,0 +1,39 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Input/Devices/DisplayClusterInputDeviceBase.h" +#include "Input/Devices/DisplayClusterInputDeviceTraits.h" + +struct FDisplayClusterConfigInput; + + +/** + * VRPN analog device data holder. Responsible for data serialization and deserialization. + */ +class FDisplayClusterVrpnAnalogInputDataHolder + : public FDisplayClusterInputDeviceBase +{ +public: + FDisplayClusterVrpnAnalogInputDataHolder(const FDisplayClusterConfigInput& config); + virtual ~FDisplayClusterVrpnAnalogInputDataHolder(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterInputDevice + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Initialize() override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterStringSerializable + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FString SerializeToString() const override final; + virtual bool DeserializeFromString(const FString& data) override final; + +private: + // Serialization constants + static constexpr auto SerializationDelimiter = TEXT("@"); + static constexpr auto SerializationItems = 2; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.cpp new file mode 100644 index 000000000000..b04c0595b8b1 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.cpp @@ -0,0 +1,75 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterVrpnAnalogInputDevice.h" + +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/DisplayClusterLog.h" + +#include "DisplayClusterStrings.h" + + +FDisplayClusterVrpnAnalogInputDevice::FDisplayClusterVrpnAnalogInputDevice(const FDisplayClusterConfigInput& config) : + FDisplayClusterVrpnAnalogInputDataHolder(config) +{ +} + +FDisplayClusterVrpnAnalogInputDevice::~FDisplayClusterVrpnAnalogInputDevice() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterVrpnAnalogInputDevice::Update() +{ + if (DevImpl) + { + UE_LOG(LogDisplayClusterInputVRPN, Verbose, TEXT("Updating device: %s"), *GetId()); + DevImpl->mainloop(); + } +} + +bool FDisplayClusterVrpnAnalogInputDevice::Initialize() +{ + FString addr; + if (!DisplayClusterHelpers::str::ExtractParam(ConfigData.Params, FString(DisplayClusterStrings::cfg::data::input::Address), addr)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - device address not found"), *ToString()); + return false; + } + + // Instantiate device implementation + DevImpl.Reset(new vrpn_Analog_Remote(TCHAR_TO_UTF8(*addr))); + + // Register update handler + if (DevImpl->register_change_handler(this, &FDisplayClusterVrpnAnalogInputDevice::HandleAnalogDevice) != 0) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - couldn't register VRPN change handler"), *ToString()); + return false; + } + + // Base initialization + return FDisplayClusterVrpnAnalogInputDataHolder::Initialize(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterVrpnAnalogInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +void VRPN_CALLBACK FDisplayClusterVrpnAnalogInputDevice::HandleAnalogDevice(void * userData, vrpn_ANALOGCB const an) +{ + auto pDev = reinterpret_cast(userData); + + for (int32 i = 0; i < an.num_channel; ++i) + { + auto pItem = pDev->DeviceData.Find(i); + if (!pItem) + { + pItem = &pDev->DeviceData.Add(i); + } + + pItem->axisValue = static_cast(an.channel[i]); + UE_LOG(LogDisplayClusterInputVRPN, VeryVerbose, TEXT("Axis %s:%d - %f"), *pDev->GetId(), i, pItem->axisValue); + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.h new file mode 100644 index 000000000000..c038a7b0c483 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.h @@ -0,0 +1,38 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterVrpnAnalogInputDataHolder.h" + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +#include "vrpn/vrpn_Analog.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif + + +/** + * VRPN analog device implementation + */ +class FDisplayClusterVrpnAnalogInputDevice + : public FDisplayClusterVrpnAnalogInputDataHolder +{ +public: + FDisplayClusterVrpnAnalogInputDevice(const FDisplayClusterConfigInput& config); + virtual ~FDisplayClusterVrpnAnalogInputDevice(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterInputDevice + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void Update() override; + virtual bool Initialize() override; + +private: + // Data update handler + static void VRPN_CALLBACK HandleAnalogDevice(void *userData, vrpn_ANALOGCB const tr); + +private: + // The device (PIMPL) + TUniquePtr DevImpl; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputData.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputData.h new file mode 100644 index 000000000000..cf8aae666bde --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputData.h @@ -0,0 +1,15 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + + +/** + * VRPN button device data type + */ +struct FDisplayClusterVrpnButtonChannelData +{ + bool btnStateOld; + bool btnStateNew; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDataHolder.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDataHolder.cpp new file mode 100644 index 000000000000..ca414a450aea --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDataHolder.cpp @@ -0,0 +1,62 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterVrpnButtonInputDataHolder.h" +#include "Misc/DisplayClusterLog.h" + + +FDisplayClusterVrpnButtonInputDataHolder::FDisplayClusterVrpnButtonInputDataHolder(const FDisplayClusterConfigInput& config) : + FDisplayClusterInputDeviceBase(config) +{ +} + +FDisplayClusterVrpnButtonInputDataHolder::~FDisplayClusterVrpnButtonInputDataHolder() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterVrpnButtonInputDataHolder::Initialize() +{ + return true; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterStringSerializable +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterVrpnButtonInputDataHolder::SerializeToString() const +{ + FString result; + result.Reserve(64); + + for (auto it = DeviceData.CreateConstIterator(); it; ++it) + { + result += FString::Printf(TEXT("%d%s%d%s%d%s"), it->Key, SerializationDelimiter, it->Value.btnStateOld, SerializationDelimiter, it->Value.btnStateNew, SerializationDelimiter); + } + + return result; +} + +bool FDisplayClusterVrpnButtonInputDataHolder::DeserializeFromString(const FString& data) +{ + TArray parsed; + data.ParseIntoArray(parsed, SerializationDelimiter); + + if (parsed.Num() % SerializationItems) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("Wrong items amount after deserialization [%s]"), *data); + return false; + } + + for (int i = 0; i < parsed.Num(); i += SerializationItems) + { + const int ch = FCString::Atoi(*parsed[i]); + const bool stateOld = (FCString::Atoi(*parsed[i + 1]) != 0); + const bool stateNew = (FCString::Atoi(*parsed[i + 2]) != 0); + DeviceData.Add(ch, FDisplayClusterVrpnButtonChannelData{ stateOld, stateNew }); + } + + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDataHolder.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDataHolder.h new file mode 100644 index 000000000000..f3ddaf4c1440 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDataHolder.h @@ -0,0 +1,38 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Input/Devices/DisplayClusterInputDeviceTraits.h" +#include "Input/Devices/DisplayClusterInputDeviceBase.h" + +#include "CoreMinimal.h" + + +/** + * VRPN button device data holder. Responsible for data serialization and deserialization. + */ +class FDisplayClusterVrpnButtonInputDataHolder + : public FDisplayClusterInputDeviceBase +{ +public: + FDisplayClusterVrpnButtonInputDataHolder(const FDisplayClusterConfigInput& config); + virtual ~FDisplayClusterVrpnButtonInputDataHolder(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterInputDevice + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Initialize() override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterStringSerializable + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FString SerializeToString() const override final; + virtual bool DeserializeFromString(const FString& data) override final; + +private: + // Serialization constants + static constexpr auto SerializationDelimiter = TEXT("@"); + static constexpr auto SerializationItems = 3; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.cpp new file mode 100644 index 000000000000..eef4de74fa01 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.cpp @@ -0,0 +1,91 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterVrpnButtonInputDevice.h" + +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/DisplayClusterLog.h" + +#include "DisplayClusterStrings.h" + + +FDisplayClusterVrpnButtonInputDevice::FDisplayClusterVrpnButtonInputDevice(const FDisplayClusterConfigInput& config) : + FDisplayClusterVrpnButtonInputDataHolder(config) +{ +} + +FDisplayClusterVrpnButtonInputDevice::~FDisplayClusterVrpnButtonInputDevice() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterVrpnButtonInputDevice::PreUpdate() +{ + // Update 'old' states before calling mainloop + for (auto it = DeviceData.CreateIterator(); it; ++it) + { + it->Value.btnStateOld = it->Value.btnStateNew; + } +} + +void FDisplayClusterVrpnButtonInputDevice::Update() +{ + if (DevImpl) + { + UE_LOG(LogDisplayClusterInputVRPN, Verbose, TEXT("Updating device: %s"), *GetId()); + DevImpl->mainloop(); + } +} + +bool FDisplayClusterVrpnButtonInputDevice::Initialize() +{ + FString addr; + if (!DisplayClusterHelpers::str::ExtractParam(ConfigData.Params, FString(DisplayClusterStrings::cfg::data::input::Address), addr)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - device address not found"), *ToString()); + return false; + } + + // Instantiate device implementation + DevImpl.Reset(new vrpn_Button_Remote(TCHAR_TO_UTF8(*addr))); + // Register update handler + if(DevImpl->register_change_handler(this, &FDisplayClusterVrpnButtonInputDevice::HandleButtonDevice) != 0) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - couldn't register VRPN change handler"), *ToString()); + return false; + } + + // Base initialization + return FDisplayClusterVrpnButtonInputDataHolder::Initialize(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterVrpnButtonInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +void VRPN_CALLBACK FDisplayClusterVrpnButtonInputDevice::HandleButtonDevice(void *userData, vrpn_BUTTONCB const b) +{ + auto pDev = reinterpret_cast(userData); + + auto pItem = pDev->DeviceData.Find(b.button); + if (!pItem) + { + pItem = &pDev->DeviceData.Add(b.button); + // Explicit initial old state set + pItem->btnStateOld = false; + } + + //@note: Actually the button can change state for several time during one update cycle. For example + // it could change 0->1->0. Then we will send only the latest state and as a result the state + // change won't be processed. I don't process such situations because it's not ok if button + // changes the state so quickly. It's probably a contact shiver or something else. Normal button + // usage will lead to state change separation between update frames. + + + // Convert button state from int to bool here. Actually VRPN has only two states for + // buttons (0-released, 1-pressed) but still uses int32 type for the state. + pItem->btnStateNew = (b.state != 0); + UE_LOG(LogDisplayClusterInputVRPN, VeryVerbose, TEXT("Button %s:%d - %d"), *pDev->GetId(), b.button, b.state); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.h new file mode 100644 index 000000000000..c955047d88ca --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.h @@ -0,0 +1,39 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterVrpnButtonInputDataHolder.h" + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +#include "vrpn/vrpn_Button.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif + + +/** + * VRPN button device implementation + */ +class FDisplayClusterVrpnButtonInputDevice + : public FDisplayClusterVrpnButtonInputDataHolder +{ +public: + FDisplayClusterVrpnButtonInputDevice(const FDisplayClusterConfigInput& config); + virtual ~FDisplayClusterVrpnButtonInputDevice(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterInputDevice + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void PreUpdate() override; + virtual void Update() override; + virtual bool Initialize() override; + +private: + // Data update handler + static void VRPN_CALLBACK HandleButtonDevice(void *userData, vrpn_BUTTONCB const b); + +private: + // The device (PIMPL) + TUniquePtr DevImpl; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputData.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputData.h new file mode 100644 index 000000000000..91f4fd3594ac --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputData.h @@ -0,0 +1,15 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + + +/** + * VRPN tracker device data type + */ +struct FDisplayClusterVrpnTrackerChannelData +{ + FVector trLoc; + FQuat trQuat; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDataHolder.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDataHolder.cpp new file mode 100644 index 000000000000..873240f19c0a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDataHolder.cpp @@ -0,0 +1,93 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterVrpnTrackerInputDataHolder.h" +#include "Misc/DisplayClusterLog.h" + + +namespace +{ + // Create a FQuat from a string that is in the same format as generated by + // FQuat::ToString. + // FQuat is missing InitializeFromString member function. + FQuat QuatFromString(const FString& InSourceString) + { + FQuat Result; + const bool bSuccessful + = FParse::Value( *InSourceString, TEXT("X="), Result.X ) + && FParse::Value( *InSourceString, TEXT("Y="), Result.Y ) + && FParse::Value( *InSourceString, TEXT("Z="), Result.Z ) + && FParse::Value( *InSourceString, TEXT("W="), Result.W ); + + if (!bSuccessful) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("Parsing FQuat from string '%s' failed!"), *InSourceString); + Result = FQuat::Identity; + } + + return Result; + } + +} // namespace + + + +FDisplayClusterVrpnTrackerInputDataHolder::FDisplayClusterVrpnTrackerInputDataHolder(const FDisplayClusterConfigInput& config) : + FDisplayClusterInputDeviceBase(config) +{ +} + +FDisplayClusterVrpnTrackerInputDataHolder::~FDisplayClusterVrpnTrackerInputDataHolder() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterVrpnTrackerInputDataHolder::Initialize() +{ + return true; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterStringSerializable +////////////////////////////////////////////////////////////////////////////////////////////// +FString FDisplayClusterVrpnTrackerInputDataHolder::SerializeToString() const +{ + FString result; + result.Reserve(256); + + for (auto it = DeviceData.CreateConstIterator(); it; ++it) + { + result += FString::Printf(TEXT("%d%s%s%s%s%s"), + it->Key, SerializationDelimiter, *it->Value.trLoc.ToString(), SerializationDelimiter, *it->Value.trQuat.ToString(), SerializationDelimiter); + } + + return result; +} + +bool FDisplayClusterVrpnTrackerInputDataHolder::DeserializeFromString(const FString& data) +{ + TArray parsed; + data.ParseIntoArray(parsed, SerializationDelimiter); + + if (parsed.Num() % SerializationItems) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("Wrong items amount after deserialization [%s]"), *data); + return false; + } + + for (int i = 0; i < parsed.Num(); i += SerializationItems) + { + const int ch = FCString::Atoi(*parsed[i]); + FVector loc; + FQuat quat = QuatFromString(parsed[i + 2]); + loc.InitFromString(parsed[i + 1]); + + DeviceData.Add(ch, FDisplayClusterVrpnTrackerChannelData{ loc, quat }); + } + + return true; +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDataHolder.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDataHolder.h new file mode 100644 index 000000000000..d4eb241ea027 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDataHolder.h @@ -0,0 +1,38 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Input/Devices/DisplayClusterInputDeviceTraits.h" +#include "Input/Devices/DisplayClusterInputDeviceBase.h" + +#include "CoreMinimal.h" + + +/** + * VRPN tracker device data holder. Responsible for data serialization and deserialization. + */ +class FDisplayClusterVrpnTrackerInputDataHolder + : public FDisplayClusterInputDeviceBase +{ +public: + FDisplayClusterVrpnTrackerInputDataHolder(const FDisplayClusterConfigInput& config); + virtual ~FDisplayClusterVrpnTrackerInputDataHolder(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterInputDevice + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Initialize() override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterStringSerializable + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FString SerializeToString() const override final; + virtual bool DeserializeFromString(const FString& data) override final; + +private: + // Serialization constants + static constexpr auto SerializationDelimiter = TEXT("@"); + static constexpr auto SerializationItems = 3; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.cpp new file mode 100644 index 000000000000..511f0f198176 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.cpp @@ -0,0 +1,252 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterVrpnTrackerInputDevice.h" + +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/DisplayClusterLog.h" + +#include "DisplayClusterBuildConfig.h" +#include "DisplayClusterStrings.h" + + +FDisplayClusterVrpnTrackerInputDevice::FDisplayClusterVrpnTrackerInputDevice(const FDisplayClusterConfigInput& config) : + FDisplayClusterVrpnTrackerInputDataHolder(config) +{ +} + +FDisplayClusterVrpnTrackerInputDevice::~FDisplayClusterVrpnTrackerInputDevice() +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterVrpnTrackerInputDevice::Update() +{ + if (DevImpl) + { + UE_LOG(LogDisplayClusterInputVRPN, Verbose, TEXT("Updating device: %s"), *GetId()); + DevImpl->mainloop(); + } +} + +void FDisplayClusterVrpnTrackerInputDevice::PostUpdate() +{ + // Perform coordinates conversion + for (auto it = DeviceData.CreateIterator(); it; ++it) + { + if (DirtyMap.Contains(it->Key)) + { + // Convert data from updated channels only + if (DirtyMap[it->Key] == true) + { + TransformCoordinates(it->Value); + DirtyMap[it->Key] = false; + } + } + } +} + +bool FDisplayClusterVrpnTrackerInputDevice::Initialize() +{ + FString addr; + if (!DisplayClusterHelpers::str::ExtractParam(ConfigData.Params, DisplayClusterStrings::cfg::data::input::Address, addr)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - device address not found"), *ToString()); + return false; + } + + // Instantiate device implementation + DevImpl.Reset(new vrpn_Tracker_Remote(TCHAR_TO_UTF8(*addr))); + + // Register update handler + if (DevImpl->register_change_handler(this, &FDisplayClusterVrpnTrackerInputDevice::HandleTrackerDevice) != 0) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - couldn't register VRPN change handler"), *ToString()); + return false; + } + + // Extract tracker location + FString loc; + if (!DisplayClusterHelpers::str::ExtractParam(ConfigData.Params, DisplayClusterStrings::cfg::data::Loc, loc, false)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - tracker origin location not found"), *ToString()); + return false; + } + + // Extract tracker rotation + FString rot; + if (!DisplayClusterHelpers::str::ExtractParam(ConfigData.Params, DisplayClusterStrings::cfg::data::Rot, rot, false)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - tracker origin rotation not found"), *ToString()); + return false; + } + + // Parse location + if (!OriginLoc.InitFromString(loc)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - unable to parse the tracker origin location"), *ToString()); + return false; + } + + // Parse rotation + FRotator originRot; + if (!originRot.InitFromString(rot)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - unable to parse the tracker origin rotation"), *ToString()); + return false; + } + else + { + OriginQuat = originRot.Quaternion(); + } + + // Parse 'right' axis mapping + FString right; + if (!DisplayClusterHelpers::str::ExtractParam(ConfigData.Params, DisplayClusterStrings::cfg::data::input::Right, right)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - 'right' axis mapping not found"), *ToString()); + return false; + } + + // Parse 'forward' axis mapping + FString front; + if (!DisplayClusterHelpers::str::ExtractParam(ConfigData.Params, DisplayClusterStrings::cfg::data::input::Front, front)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - 'front' axis mapping not found"), *ToString()); + return false; + } + + // Parse 'up' axis mapping + FString up; + if (!DisplayClusterHelpers::str::ExtractParam(ConfigData.Params, DisplayClusterStrings::cfg::data::input::Up, up)) + { + UE_LOG(LogDisplayClusterInputVRPN, Error, TEXT("%s - 'up' axis mapping not found"), *ToString()); + return false; + } + + // Store mapping rules + AxisFront = String2Map(front, AxisMapType::X); + AxisRight = String2Map(right, AxisMapType::Y); + AxisUp = String2Map(up, AxisMapType::Z); + AxisW = ComputeAxisW(AxisFront, AxisRight, AxisUp); + + // Base initialization + return FDisplayClusterVrpnTrackerInputDataHolder::Initialize(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterVrpnTrackerInputDevice +////////////////////////////////////////////////////////////////////////////////////////////// +namespace +{ + // Location + float LocGetX(const FVector& loc) { return loc.X; } + float LocGetNX(const FVector& loc) { return -loc.X; } + + float LocGetY(const FVector& loc) { return loc.Y; } + float LocGetNY(const FVector& loc) { return -loc.Y; } + + float LocGetZ(const FVector& loc) { return loc.Z; } + float LocGetNZ(const FVector& loc) { return -loc.Z; } + + // Rotation + float RotGetX(const FQuat& quat) { return quat.X; } + float RotGetNX(const FQuat& quat) { return -quat.X; } + + float RotGetY(const FQuat& quat) { return quat.Y; } + float RotGetNY(const FQuat& quat) { return -quat.Y; } + + float RotGetZ(const FQuat& quat) { return quat.Z; } + float RotGetNZ(const FQuat& quat) { return -quat.Z; } + + float RotGetW(const FQuat& quat) { return quat.W; } + float RotGetNW(const FQuat& quat) { return -quat.W; } + + typedef float(*TLocGetter)(const FVector& loc); + typedef float(*TRotGetter)(const FQuat& rot); +} + +FDisplayClusterVrpnTrackerInputDevice::AxisMapType FDisplayClusterVrpnTrackerInputDevice::String2Map(const FString& str, const AxisMapType defaultMap) const +{ + const FString mapVal = str.ToLower(); + + if (mapVal == DisplayClusterStrings::cfg::data::input::MapX) + return AxisMapType::X; + else if (mapVal == DisplayClusterStrings::cfg::data::input::MapNX) + return AxisMapType::NX; + else if (mapVal == DisplayClusterStrings::cfg::data::input::MapY) + return AxisMapType::Y; + else if (mapVal == DisplayClusterStrings::cfg::data::input::MapNY) + return AxisMapType::NY; + else if (mapVal == DisplayClusterStrings::cfg::data::input::MapZ) + return AxisMapType::Z; + else if (mapVal == DisplayClusterStrings::cfg::data::input::MapNZ) + return AxisMapType::NZ; + else + { + UE_LOG(LogDisplayClusterInputVRPN, Warning, TEXT("Unknown mapping type: %s"), *str); + } + + return defaultMap; +} + +FDisplayClusterVrpnTrackerInputDevice::AxisMapType FDisplayClusterVrpnTrackerInputDevice::ComputeAxisW(const AxisMapType front, const AxisMapType right, const AxisMapType up) const +{ + int det = 1; + + if (front == AxisMapType::NX || front == AxisMapType::NY || front == AxisMapType::NZ) + det *= -1; + + if (right == AxisMapType::NX || right == AxisMapType::NY || right == AxisMapType::NZ) + det *= -1; + + if (up == AxisMapType::NX || up == AxisMapType::NY || up == AxisMapType::NZ) + det *= -1; + + return (det < 0) ? AxisMapType::NW : AxisMapType::W; +} + +FVector FDisplayClusterVrpnTrackerInputDevice::GetMappedLocation(const FVector& loc, const AxisMapType front, const AxisMapType right, const AxisMapType up) const +{ + static TLocGetter funcs[] = { &LocGetX, &LocGetNX, &LocGetY, &LocGetNY, &LocGetZ, &LocGetNZ }; + return FVector(funcs[front](loc), funcs[right](loc), funcs[up](loc)); +} + +FQuat FDisplayClusterVrpnTrackerInputDevice::GetMappedQuat(const FQuat& quat, const AxisMapType front, const AxisMapType right, const AxisMapType up, const AxisMapType axisW) const +{ + static TRotGetter funcs[] = { &RotGetX, &RotGetNX, &RotGetY, &RotGetNY, &RotGetZ, &RotGetNZ, &RotGetW, &RotGetNW }; + return FQuat(funcs[front](quat), funcs[right](quat), funcs[up](quat), -quat.W);// funcs[axisW](quat)); +} + +void FDisplayClusterVrpnTrackerInputDevice::TransformCoordinates(FDisplayClusterVrpnTrackerChannelData &data) const +{ + UE_LOG(LogDisplayClusterInputVRPN, VeryVerbose, TEXT("TransformCoordinates old: "), *data.trLoc.ToString(), *data.trQuat.ToString()); + + // Transform location + data.trLoc = OriginLoc + GetMappedLocation(data.trLoc, AxisFront, AxisRight, AxisUp); + data.trLoc *= 100.f; + + // Transform rotation + data.trQuat = OriginQuat * data.trQuat; + data.trQuat = GetMappedQuat(data.trQuat, AxisFront, AxisRight, AxisUp, AxisW); + + UE_LOG(LogDisplayClusterInputVRPN, VeryVerbose, TEXT("TransformCoordinates new: "), *data.trLoc.ToString(), *data.trQuat.ToString()); +} + +void VRPN_CALLBACK FDisplayClusterVrpnTrackerInputDevice::HandleTrackerDevice(void *userData, vrpn_TRACKERCB const tr) +{ + auto pDev = reinterpret_cast(userData); + + const FVector loc(tr.pos[0], tr.pos[1], tr.pos[2]); + const FQuat quat(tr.quat[0], tr.quat[1], tr.quat[2], tr.quat[3]); + + const FDisplayClusterVrpnTrackerChannelData data{ loc, quat }; + auto pItem = &pDev->DeviceData.Add(tr.sensor, data); + + pDev->DirtyMap.Add(static_cast(tr.sensor), true); + + UE_LOG(LogDisplayClusterInputVRPN, VeryVerbose, TEXT("Tracker %s:%d {loc %s} {rot %s}"), *pDev->GetId(), tr.sensor, *pItem->trLoc.ToString(), *pItem->trQuat.ToString()); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.h new file mode 100644 index 000000000000..945e78e8ec65 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.h @@ -0,0 +1,68 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterVrpnTrackerInputDataHolder.h" + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +#include "vrpn/vrpn_Tracker.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif + + +/** + * VRPN tracker device implementation + */ +class FDisplayClusterVrpnTrackerInputDevice + : public FDisplayClusterVrpnTrackerInputDataHolder +{ +public: + FDisplayClusterVrpnTrackerInputDevice(const FDisplayClusterConfigInput& config); + virtual ~FDisplayClusterVrpnTrackerInputDevice(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterInputDevice + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void Update() override; + virtual void PostUpdate() override; + virtual bool Initialize() override; + +protected: + // Per-channel dirty state + TMap DirtyMap; + + // Transform form tracker space to DisplayCluster space + void TransformCoordinates(FDisplayClusterVrpnTrackerChannelData& data) const; + + +private: + // Tracker origin + FVector OriginLoc = FVector::ZeroVector; + FQuat OriginQuat = FQuat::Identity; + +private: + // Coordinate system conversion + enum AxisMapType { X = 0, NX, Y, NY, Z, NZ, W, NW }; + + // Internal conversion helpers + AxisMapType String2Map(const FString& str, const AxisMapType defaultMap) const; + AxisMapType ComputeAxisW(const AxisMapType front, const AxisMapType right, const AxisMapType up) const; + FVector GetMappedLocation(const FVector& loc, const AxisMapType front, const AxisMapType right, const AxisMapType up) const; + FQuat GetMappedQuat(const FQuat& quat, const AxisMapType front, const AxisMapType right, const AxisMapType up, const AxisMapType axisW) const; + + // Tracker space to DisplayCluster space axis mapping + AxisMapType AxisFront; + AxisMapType AxisRight; + AxisMapType AxisUp; + AxisMapType AxisW; + +private: + // Data update handler + static void VRPN_CALLBACK HandleTrackerDevice(void *userData, vrpn_TRACKERCB const tr); + +private: + // The device (PIMPL) + TUniquePtr DevImpl; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/DisplayClusterInputManager.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/DisplayClusterInputManager.cpp new file mode 100644 index 000000000000..71f054c0dbc8 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/DisplayClusterInputManager.cpp @@ -0,0 +1,488 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterInputManager.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Config/IPDisplayClusterConfigManager.h" + +#include "Devices/VRPN/Analog/DisplayClusterVrpnAnalogInputDevice.h" +#include "Devices/VRPN/Button/DisplayClusterVrpnButtonInputDevice.h" +#include "Devices/VRPN/Tracker/DisplayClusterVrpnTrackerInputDevice.h" + +#include "Misc/DisplayClusterLog.h" +#include "DisplayClusterGameMode.h" +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +FDisplayClusterInputManager::FDisplayClusterInputManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); +} + +FDisplayClusterInputManager::~FDisplayClusterInputManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterInputManager::Init(EDisplayClusterOperationMode OperationMode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + return true; +} + +void FDisplayClusterInputManager::Release() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); +} + +bool FDisplayClusterInputManager::StartSession(const FString& configPath, const FString& nodeId) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + ConfigPath = configPath; + ClusterNodeId = nodeId; + + if (!InitDevices()) + { + UE_LOG(LogDisplayClusterInput, Error, TEXT("Couldn't initialize input devices")); + return false; + } + + return true; +} + +void FDisplayClusterInputManager::EndSession() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + ReleaseDevices(); +} + +bool FDisplayClusterInputManager::StartScene(UWorld* pWorld) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + check(pWorld); + CurrentWorld = pWorld; + + return true; +} + +void FDisplayClusterInputManager::EndScene() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); +} + +void FDisplayClusterInputManager::PreTick(float DeltaSeconds) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterInputManager +////////////////////////////////////////////////////////////////////////////////////////////// +// Basic functionality (device amount) +uint32 FDisplayClusterInputManager::GetAxisDeviceAmount() const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetDeviceAmount_impl(); +} + +uint32 FDisplayClusterInputManager::GetButtonDeviceAmount() const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetDeviceAmount_impl(); +} + +uint32 FDisplayClusterInputManager::GetTrackerDeviceAmount() const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetDeviceAmount_impl(); +} + +// Access to the device lists +bool FDisplayClusterInputManager::GetAxisDeviceIds(TArray& ids) const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetDeviceIds_impl(ids); +} + +bool FDisplayClusterInputManager::GetButtonDeviceIds(TArray& ids) const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetDeviceIds_impl(ids); +} + +bool FDisplayClusterInputManager::GetTrackerDeviceIds(TArray& ids) const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetDeviceIds_impl(ids); +} + + +// Button data access +bool FDisplayClusterInputManager::GetButtonState(const FString& devId, const uint8 btn, bool& curState) const +{ + FDisplayClusterVrpnButtonChannelData data; + if (GetButtonData(devId, btn, data)) + { + curState = data.btnStateNew; + return true; + } + + return false; +} + +bool FDisplayClusterInputManager::IsButtonPressed(const FString& devId, const uint8 btn, bool& curPressed) const +{ + bool btnState; + if (GetButtonState(devId, btn, btnState)) + { + curPressed = (btnState == true); + return true; + } + + return false; +} + +bool FDisplayClusterInputManager::IsButtonReleased(const FString& devId, const uint8 btn, bool& curReleased) const +{ + bool btnState; + if (GetButtonState(devId, btn, btnState)) + { + curReleased = (btnState == false); + return true; + } + + return false; +} + +bool FDisplayClusterInputManager::WasButtonPressed(const FString& devId, const uint8 btn, bool& wasPressed) const +{ + FDisplayClusterVrpnButtonChannelData data; + if (GetButtonData(devId, btn, data)) + { + wasPressed = (data.btnStateOld == false && data.btnStateNew == true); + return true; + } + + return false; +} + +bool FDisplayClusterInputManager::WasButtonReleased(const FString& devId, const uint8 btn, bool& wasReleased) const +{ + FDisplayClusterVrpnButtonChannelData data; + if (GetButtonData(devId, btn, data)) + { + wasReleased = (data.btnStateOld == true && data.btnStateNew == false); + return true; + } + + return false; +} + +// Axes data access +bool FDisplayClusterInputManager::GetAxis(const FString& devId, const uint8 axis, float& value) const +{ + FDisplayClusterVrpnAnalogChannelData data; + if (GetAxisData(devId, axis, data)) + { + value = data.axisValue; + return true; + } + + return false; +} + +// Tracking data access +bool FDisplayClusterInputManager::GetTrackerLocation(const FString& devId, const uint8 tr, FVector& location) const +{ + FDisplayClusterVrpnTrackerChannelData data; + if (GetTrackerData(devId, tr, data)) + { + location = data.trLoc; + return true; + } + + return false; +} + +bool FDisplayClusterInputManager::GetTrackerQuat(const FString& devId, const uint8 tr, FQuat& rotation) const +{ + FDisplayClusterVrpnTrackerChannelData data; + if (GetTrackerData(devId, tr, data)) + { + rotation = data.trQuat; + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterInputManager +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterInputManager::Update() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + // Perform input update on master only. Slaves' state will be replicated later. + if (GDisplayCluster->GetPrivateClusterMgr()->IsMaster()) + { + UE_LOG(LogDisplayClusterInput, Verbose, TEXT("Input update started")); + { + FScopeLock ScopeLock(&InternalsSyncScope); + + // Pre-Update + UE_LOG(LogDisplayClusterInput, Verbose, TEXT("Input pre-update...")); + for (auto classIt = Devices.CreateIterator(); classIt; ++classIt) + { + for (auto devIt = classIt->Value.CreateConstIterator(); devIt; ++devIt) + { + devIt->Value->PreUpdate(); + } + } + + // Update + UE_LOG(LogDisplayClusterInput, Verbose, TEXT("Input update...")); + for (auto classIt = Devices.CreateIterator(); classIt; ++classIt) + { + for (auto devIt = classIt->Value.CreateConstIterator(); devIt; ++devIt) + { + devIt->Value->Update(); + } + } + + // Post-Update + for (auto classIt = Devices.CreateIterator(); classIt; ++classIt) + { + for (auto devIt = classIt->Value.CreateConstIterator(); devIt; ++devIt) + { + devIt->Value->PostUpdate(); + } + } + } + UE_LOG(LogDisplayClusterInput, Verbose, TEXT("Input update finished")); + + // Update input data cache for slave nodes + UpdateInputDataCache(); + } +} + +void FDisplayClusterInputManager::ExportInputData(FDisplayClusterMessage::DataType& data) const +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + // Get data from cache + data = PackedTransferData; +} + +void FDisplayClusterInputManager::ImportInputData(const FDisplayClusterMessage::DataType& data) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + FScopeLock ScopeLock(&InternalsSyncScope); + + for (auto rec : data) + { + FString strClassId; + FString strDevId; + if (rec.Key.Split(FString(SerializationDeviceTypeNameDelimiter), &strClassId, &strDevId)) + { + UE_LOG(LogDisplayClusterInput, VeryVerbose, TEXT("Deserializing input device: <%s, %s>"), *rec.Key, *rec.Value); + + int classId = FCString::Atoi(*strClassId); + if (Devices.Contains(classId)) + { + if (Devices[classId].Contains(strDevId)) + { + Devices[classId][strDevId]->DeserializeFromString(rec.Value); + } + } + } + } +} + + +bool FDisplayClusterInputManager::GetAxisData(const FString& devId, const uint8 channel, FDisplayClusterVrpnAnalogChannelData& data) const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetChannelData_impl(devId, channel, data); +} + +bool FDisplayClusterInputManager::GetButtonData(const FString& devId, const uint8 channel, FDisplayClusterVrpnButtonChannelData& data) const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetChannelData_impl(devId, channel, data); +} + +bool FDisplayClusterInputManager::GetTrackerData(const FString& devId, const uint8 channel, FDisplayClusterVrpnTrackerChannelData& data) const +{ + FScopeLock ScopeLock(&InternalsSyncScope); + return GetChannelData_impl(devId, channel, data); +} + +template +uint32 FDisplayClusterInputManager::GetDeviceAmount_impl() const +{ + if (!Devices.Contains(DevTypeID)) + { + return 0; + } + + return static_cast(Devices[DevTypeID].Num()); +} + +template +bool FDisplayClusterInputManager::GetDeviceIds_impl(TArray& ids) const +{ + if (!Devices.Contains(DevTypeID)) + { + return false; + } + + Devices[DevTypeID].GenerateKeyArray(ids); + return true; +} + +template +bool FDisplayClusterInputManager::GetChannelData_impl(const FString& devId, const uint8 channel, typename display_cluster_input_device_traits::dev_channel_data_type& data) const +{ + if (!Devices.Contains(DevTypeID)) + { + return false; + } + + if (!Devices[DevTypeID].Contains(devId)) + { + return false; + } + + return static_cast*>(Devices[DevTypeID][devId].Get())->GetChannelData(channel, data); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterInputManager +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterInputManager::InitDevices() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + FScopeLock ScopeLock(&InternalsSyncScope); + + UE_LOG(LogDisplayClusterInput, Log, TEXT("Initializing input devices...")); + + const TArray cfgInputDevs = GDisplayCluster->GetPrivateConfigMgr()->GetInputDevices(); + + for (auto& cfgDev : cfgInputDevs) + { + UE_LOG(LogDisplayClusterInput, Verbose, TEXT("Creating input device: %s"), *cfgDev.ToString()); + + IDisplayClusterInputDevice* pDev = nullptr; + + if (cfgDev.Type.Compare(FString(DisplayClusterStrings::cfg::data::input::DeviceAnalog), ESearchCase::IgnoreCase) == 0) + { + if (GDisplayCluster->GetPrivateClusterMgr()->IsMaster()) + { + pDev = new FDisplayClusterVrpnAnalogInputDevice(cfgDev); + } + else + { + pDev = new FDisplayClusterVrpnAnalogInputDataHolder(cfgDev); + } + } + else if (cfgDev.Type.Compare(FString(DisplayClusterStrings::cfg::data::input::DeviceButtons), ESearchCase::IgnoreCase) == 0) + { + if (GDisplayCluster->GetPrivateClusterMgr()->IsMaster()) + { + pDev = new FDisplayClusterVrpnButtonInputDevice(cfgDev); + } + else + { + pDev = new FDisplayClusterVrpnButtonInputDataHolder(cfgDev); + } + } + else if (cfgDev.Type.Compare(FString(DisplayClusterStrings::cfg::data::input::DeviceTracker), ESearchCase::IgnoreCase) == 0) + { + if (GDisplayCluster->GetPrivateClusterMgr()->IsMaster()) + { + pDev = new FDisplayClusterVrpnTrackerInputDevice(cfgDev); + } + else + { + pDev = new FDisplayClusterVrpnTrackerInputDataHolder(cfgDev); + } + } + else + { + UE_LOG(LogDisplayClusterInput, Error, TEXT("Unsupported device type: %s"), *cfgDev.Type); + continue; + } + + if (pDev && pDev->Initialize()) + { + UE_LOG(LogDisplayClusterInput, Log, TEXT("Adding device: %s"), *pDev->ToString()); + + auto pDevMap = Devices.Find(pDev->GetTypeId()); + if (!pDevMap) + { + pDevMap = &Devices.Add(pDev->GetTypeId()); + } + + pDevMap->Add(cfgDev.Id, TDevice(pDev)); + } + else + { + UE_LOG(LogDisplayClusterInput, Warning, TEXT("Neither data holder nor true device was instantiated for item id: %s"), *cfgDev.Id); + + // It's safe to delete nullptr so no checking performed + delete pDev; + + //@note: Allow other devices to be initialized. User will locate the problem from logs. + //return false; + } + } + + return true; +} + +void FDisplayClusterInputManager::ReleaseDevices() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + FScopeLock ScopeLock(&InternalsSyncScope); + + UE_LOG(LogDisplayClusterInput, Log, TEXT("Releasing input subsystem...")); + + UE_LOG(LogDisplayClusterInput, Log, TEXT("Releasing input devices...")); + Devices.Empty(); +} + +void FDisplayClusterInputManager::UpdateInputDataCache() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterInput); + + FScopeLock ScopeLock(&InternalsSyncScope); + + // Clear previously cached data + PackedTransferData.Empty(PackedTransferData.Num() | 0x07); + + for (auto classIt = Devices.CreateConstIterator(); classIt; ++classIt) + { + for (auto devIt = classIt->Value.CreateConstIterator(); devIt; ++devIt) + { + const FString key = FString::Printf(TEXT("%d%s%s"), classIt->Key, SerializationDeviceTypeNameDelimiter, *devIt->Key); + const FString val = devIt->Value->SerializeToString(); + UE_LOG(LogDisplayClusterInput, VeryVerbose, TEXT("Input device %d:%s serialized: <%s, %s>"), classIt->Key, *devIt->Key, *key, *val); + PackedTransferData.Add(key, val); + } + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/DisplayClusterInputManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/DisplayClusterInputManager.h new file mode 100644 index 000000000000..fe1e34a9f599 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/DisplayClusterInputManager.h @@ -0,0 +1,119 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IPDisplayClusterInputManager.h" + +#include "CoreMinimal.h" + +#include "Devices/DisplayClusterInputDeviceTraits.h" +#include "Network/DisplayClusterMessage.h" + +struct IDisplayClusterInputDevice; +struct FDisplayClusterVrpnAnalogChannelData; +struct FDisplayClusterVrpnButtonChannelData; +struct FDisplayClusterVrpnTrackerChannelData; + + +/** + * Input manager. Implements everything related to VR input devices (VRPN, etc.) + */ +class FDisplayClusterInputManager + : public IPDisplayClusterInputManager +{ +public: + FDisplayClusterInputManager(); + virtual ~FDisplayClusterInputManager(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Init(EDisplayClusterOperationMode OperationMode) override; + virtual void Release() override; + virtual bool StartSession(const FString& configPath, const FString& nodeId) override; + virtual void EndSession() override; + virtual bool StartScene(UWorld* pWorld) override; + virtual void EndScene() override; + virtual void PreTick(float DeltaSeconds); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterInputManager + ////////////////////////////////////////////////////////////////////////////////////////////// + // Device amount + virtual uint32 GetAxisDeviceAmount() const override; + virtual uint32 GetButtonDeviceAmount() const override; + virtual uint32 GetTrackerDeviceAmount() const override; + + // Device IDs + virtual bool GetAxisDeviceIds (TArray& ids) const override; + virtual bool GetButtonDeviceIds (TArray& ids) const override; + virtual bool GetTrackerDeviceIds(TArray& ids) const override; + + // Button data access + virtual bool GetButtonState(const FString& devId, const uint8 btn, bool& curState) const override; + virtual bool IsButtonPressed(const FString& devId, const uint8 btn, bool& curPressed) const override; + virtual bool IsButtonReleased(const FString& devId, const uint8 btn, bool& curReleased) const override; + virtual bool WasButtonPressed(const FString& devId, const uint8 btn, bool& wasPressed) const override; + virtual bool WasButtonReleased(const FString& devId, const uint8 btn, bool& wasReleased) const override; + + // Axes data access + virtual bool GetAxis(const FString& devId, const uint8 axis, float& value) const override; + + // Tracking data access + virtual bool GetTrackerLocation(const FString& devId, const uint8 tr, FVector& location) const override; + virtual bool GetTrackerQuat(const FString& devId, const uint8 tr, FQuat& rotation) const override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterInputManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void Update() override; + + virtual void ExportInputData(FDisplayClusterMessage::DataType& data) const override; + virtual void ImportInputData(const FDisplayClusterMessage::DataType& data) override; + +private: + typedef TUniquePtr TDevice; + typedef TMap TDeviceClassMap; + typedef TMap TDeviceMap; + + bool InitDevices(); + void ReleaseDevices(); + void UpdateInputDataCache(); + + // Device data + bool GetAxisData (const FString& devId, const uint8 channel, FDisplayClusterVrpnAnalogChannelData& data) const; + bool GetButtonData (const FString& devId, const uint8 channel, FDisplayClusterVrpnButtonChannelData& data) const; + bool GetTrackerData(const FString& devId, const uint8 channel, FDisplayClusterVrpnTrackerChannelData& data) const; + +private: + // Input devices + TDeviceMap Devices; + // Input state data cache + FDisplayClusterMessage::DataType PackedTransferData; + // Current config path + FString ConfigPath; + // Current cluster node ID + FString ClusterNodeId; + // Current world + UWorld* CurrentWorld; + + mutable FCriticalSection InternalsSyncScope; + +private: + template + uint32 GetDeviceAmount_impl() const; + + template + bool GetDeviceIds_impl(TArray& ids) const; + + template + bool GetChannelData_impl(const FString& devId, const uint8 channel, typename display_cluster_input_device_traits::dev_channel_data_type& data) const; + +private: + static constexpr auto SerializationDeviceTypeNameDelimiter = TEXT(" "); +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/IPDisplayClusterInputManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/IPDisplayClusterInputManager.h new file mode 100644 index 000000000000..bc91976b3131 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Input/IPDisplayClusterInputManager.h @@ -0,0 +1,25 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Input/IDisplayClusterInputManager.h" +#include "IPDisplayClusterManager.h" + +#include "Network/DisplayClusterMessage.h" + + +/** + * Input manager private interface + */ +struct IPDisplayClusterInputManager + : public IDisplayClusterInputManager + , public IPDisplayClusterManager +{ + virtual ~IPDisplayClusterInputManager() + { } + + virtual void Update() = 0; + + virtual void ExportInputData(FDisplayClusterMessage::DataType& data) const = 0; + virtual void ImportInputData(const FDisplayClusterMessage::DataType& data) = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterAppExit.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterAppExit.cpp new file mode 100644 index 000000000000..9e21491252fb --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterAppExit.cpp @@ -0,0 +1,97 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterAppExit.h" +#include "DisplayClusterLog.h" +#include "Engine/GameEngine.h" + +#if WITH_EDITOR +#include "Editor/UnrealEd/Public/UnrealEdGlobals.h" +#include "Editor/UnrealEdEngine.h" +#endif + +FCriticalSection FDisplayClusterAppExit::InternalsSyncScope; + +auto FDisplayClusterAppExit::ExitTypeToStr(ExitType type) +{ + switch (type) + { + case ExitType::KillImmediately: + return TEXT("KILL"); + case ExitType::NormalSoft: + return TEXT("UE4_soft"); + case ExitType::NormalForce: + return TEXT("UE4_force"); + default: + return TEXT("unknown"); + } +} + +void FDisplayClusterAppExit::ExitApplication(ExitType exitType, const FString& strMsg) +{ + if (GEngine && GEngine->IsEditor()) + { +#if WITH_EDITOR + UE_LOG(LogDisplayClusterModule, Log, TEXT("PIE STOP: %s application quit requested: %s"), ExitTypeToStr(exitType), *strMsg); + GUnrealEd->RequestEndPlayMap(); +#endif + return; + } + else + { + FScopeLock lock(&InternalsSyncScope); + + // We process only first call. Thus we won't have a lot of requests from different socket threads. + // We also will know the first requester which may be useful in step-by-step problem solving. + static bool bRequestedBefore = false; + if (bRequestedBefore == false || exitType == ExitType::KillImmediately) + { + bRequestedBefore = true; + UE_LOG(LogDisplayClusterModule, Log, TEXT("%s application quit requested: %s"), ExitTypeToStr(exitType), *strMsg); + + GLog->Flush(); + +#if 0 + if (IsInGameThread()) + { + GLog->FlushThreadedLogs(); +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + TGuardValue GuardMainThreadBlockedOnRenderThread(GMainThreadBlockedOnRenderThread, true); +#endif + SCOPE_CYCLE_COUNTER(STAT_PumpMessages); + FPlatformMisc::PumpMessages(false); + } +#endif + + switch (exitType) + { + case ExitType::KillImmediately: + { + FProcHandle hProc = FPlatformProcess::OpenProcess(FPlatformProcess::GetCurrentProcessId()); + FPlatformProcess::TerminateProc(hProc, true); + break; + } + + case ExitType::NormalSoft: + { +//@todo: This is workaround for exit issue - crash on exit. Need to be checked on new UE versions. +// Assertion failed: NumRemoved == 1 [File:D:\work\UE4.12.5.build\Engine\Source\Runtime\CoreUObject\Private\UObject\UObjectHash.cpp] [Line: 905] &nl;&nl; + FProcHandle hProc = FPlatformProcess::OpenProcess(FPlatformProcess::GetCurrentProcessId()); + FPlatformProcess::TerminateProc(hProc, true); + break; + } + + case ExitType::NormalForce: + { + FPlatformMisc::RequestExit(true); + break; + } + + default: + { + UE_LOG(LogDisplayClusterModule, Warning, TEXT("Unknown exit type requested")); + break; + } + } + } + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterAppExit.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterAppExit.h new file mode 100644 index 000000000000..e99c7cd1e315 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterAppExit.h @@ -0,0 +1,32 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + + +/** + * Auxiliary class. Responsible for terminating application. + */ +class FDisplayClusterAppExit +{ +public: + enum class ExitType + { + // Kills current process. No resource cleaning performed. + KillImmediately, + // UE4 based soft exit (game thread). Full resource cleaning. + NormalSoft, + // UE4 game termination. Error window and dump file should appear after exit. + NormalForce + }; + +public: + static void ExitApplication(ExitType exitType, const FString& strMsg); + +private: + static auto ExitTypeToStr(ExitType type); + +private: + static FCriticalSection InternalsSyncScope; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterBarrier.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterBarrier.cpp new file mode 100644 index 000000000000..609eb9de2f2c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterBarrier.cpp @@ -0,0 +1,112 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterBarrier.h" + +#include "DisplayClusterLog.h" +#include "Engine/EngineTypes.h" + +#include + + +FDisplayClusterBarrier::FDisplayClusterBarrier(uint32 threadsAmount, const FString& name, uint32 timeout) : + Name(name), + ThreadsAmount(threadsAmount), + ThreadsLeft(threadsAmount), + IterationCounter(0), + Timeout(timeout) +{ + UE_LOG(LogDisplayClusterNetwork, Log, TEXT("Initialized barrier %s with timeout %u for threads count: %u"), *Name, Timeout, ThreadsAmount); +} + +FDisplayClusterBarrier::FDisplayClusterBarrier(uint32 threadsAmount, uint32 timeout) : + FDisplayClusterBarrier(threadsAmount, FString("noname_barrier"), timeout) +{ +} + + +FDisplayClusterBarrier::~FDisplayClusterBarrier() +{ + // Free currently blocked threads + Deactivate(); +} + +FDisplayClusterBarrier::WaitResult FDisplayClusterBarrier::Wait(double* pThreadWaitTime /*= nullptr*/, double* pBarrierWaitTime /*= nullptr*/) +{ + if (bEnabled == false) + { + UE_LOG(LogDisplayClusterNetwork, Verbose, TEXT("%s barrier is not active"), *Name); + return WaitResult::NotActive; + } + + const double threadWaitTimeStart = FPlatformTime::Seconds(); + + { + std::unique_lock lock{ Mutex }; + + size_t curIter = IterationCounter; + + if (ThreadsLeft == ThreadsAmount) + { + WaitTimeStart = FPlatformTime::Seconds(); + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("%s barrier start time: %lf"), *Name, WaitTimeStart); + } + + // Check if all threads are in front of the barrier + if (--ThreadsLeft == 0) + { + UE_LOG(LogDisplayClusterNetwork, Verbose, TEXT("%s barrier trigger!"), *Name); + ++IterationCounter; + ThreadsLeft = ThreadsAmount; + + WaitTimeFinish = FPlatformTime::Seconds(); + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("%s barrier finish time: %lf"), *Name, WaitTimeFinish); + + WaitTimeOverall = WaitTimeFinish - WaitTimeStart; + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("%s barrier overall wait time: %lf"), *Name, WaitTimeOverall); + + // This is the last node. Unblock the barrier. + CondVar.notify_all(); + } + else + { + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("%s barrier waiting, %u threads left"), *Name, ThreadsLeft); + // Not all of threads have came here. Wait. + if (!CondVar.wait_for(lock, std::chrono::milliseconds(Timeout), [this, curIter] { return curIter != IterationCounter || bEnabled == false; })) + { + //@todo: no timeout result if barrier has been disabled + UE_LOG(LogDisplayClusterNetwork, Warning, TEXT("%s barrier waiting timeout"), *Name); + return WaitResult::Timeout; + } + } + } + + const double threadWaitTimeFinish = FPlatformTime::Seconds(); + + if (pBarrierWaitTime) + *pBarrierWaitTime = WaitTimeOverall; + + if (pThreadWaitTime) + *pThreadWaitTime = threadWaitTimeFinish - threadWaitTimeStart; + + // Go ahead + return WaitResult::Ok; +} + +void FDisplayClusterBarrier::Activate() +{ + std::unique_lock lock{ Mutex }; + + IterationCounter = 0; + ThreadsLeft = ThreadsAmount; + bEnabled = true; + CondVar.notify_all(); +} + +void FDisplayClusterBarrier::Deactivate() +{ + std::unique_lock lock{ Mutex }; + + bEnabled = false; + CondVar.notify_all(); +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterBarrier.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterBarrier.h new file mode 100644 index 000000000000..6f1adcec0891 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterBarrier.h @@ -0,0 +1,59 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include +#include + + +/** + * Thread barrier + */ +class FDisplayClusterBarrier +{ +public: + explicit FDisplayClusterBarrier(uint32 threadsAmount, uint32 timeout); + explicit FDisplayClusterBarrier(uint32 threadsAmount, const FString& name, uint32 timeout); + ~FDisplayClusterBarrier(); + +public: + enum class WaitResult + { + Ok, + NotActive, + Timeout + }; + +public: + // Wait until all threads arrive + WaitResult Wait(double* pThreadWaitTime = nullptr, double* pBarrierWaitTime = nullptr); + // Enable barrier + void Activate(); + // Disable barrier (no blocking operation performed anymore) + void Deactivate(); + +private: + // Barrier name for logging + const FString Name; + + // Barrier state + bool bEnabled = true; + + // Amount of threads to wait at the barrier + const uint32 ThreadsAmount; + // Waiting threads amount + uint32 ThreadsLeft; + // Iteration counter (kind of barrier sync transaction) + size_t IterationCounter; + + std::condition_variable CondVar; + std::mutex Mutex; + + uint32 Timeout = 0; + + double WaitTimeStart = 0; + double WaitTimeFinish = 0; + double WaitTimeOverall = 0; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterHelpers.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterHelpers.h new file mode 100644 index 000000000000..ad2b81aad34d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterHelpers.h @@ -0,0 +1,206 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "CoreTypes.h" +#include "EngineUtils.h" + +#include "DisplayClusterStrings.h" + +#include "Misc/DisplayClusterTypesConverter.h" + +#include "Interfaces/IPv4/IPv4Address.h" +#include "Interfaces/IPv4/IPv4Endpoint.h" + + +class AActor; + + +namespace DisplayClusterHelpers +{ + ////////////////////////////////////////////////////////////////////////////////////////////// + // Common String helpers + ////////////////////////////////////////////////////////////////////////////////////////////// + namespace str + { + static constexpr auto StrFalse = TEXT("false"); + static constexpr auto StrTrue = TEXT("true"); + + static inline auto BoolToStr(bool bVal) + { + return (bVal ? StrTrue : StrFalse); + } + + static void DustCommandLineValue(FString& val, bool bTrimQuotes = true) + { + val.RemoveFromStart(DisplayClusterStrings::strKeyValSeparator); + + if(bTrimQuotes) + val = val.TrimQuotes(); + + val.TrimStartAndEndInline(); + } + + template + static bool ExtractCommandLineValue(const FString& line, const FString& argName, T& argVal) + { + FString tmp; + if (FParse::Value(*line, *argName, tmp, false)) + { + DustCommandLineValue(tmp, false); + argVal = FDisplayClusterTypesConverter::FromString(tmp); + return true; + } + return false; + } + + static bool ExtractParam(const FString& source, const FString& param, FString& value, bool bTrimQuotes = true) + { + // Extract device address + if (!FParse::Value(*source, *param, value, false)) + return false; + + DisplayClusterHelpers::str::DustCommandLineValue(value, bTrimQuotes); + + return true; + } + +#if 0 + bool GetPair(FString& line, FString& pair) + { + if (line.IsEmpty()) + return false; + + if (line.Split(FString(" "), &pair, &line) == false) + { + pair = line; + line.Empty(); + return true; + } + + line = line.Trim().TrimTrailing(); + pair = pair.Trim().TrimTrailing(); + + return true; + } + + bool GetKeyVal(FString& line, FString& key, FString& val) + { + FString pair; + if (GetPair(line, pair) == false) + return false; + + if (pair.Split(FString(DisplayClusterStrings::cfg::spec::KeyValSeparator), &key, &val) == false) + return false; + + key = key.Trim().TrimTrailing(); + val = val.Trim().TrimTrailing(); + + return true; + } +#endif + }; + + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Network helpers + ////////////////////////////////////////////////////////////////////////////////////////////// + namespace net + { + static bool GenIPv4Endpoint(const FString& addr, const int32 port, FIPv4Endpoint& ep) + { + FIPv4Address ipAddr; + if (!FIPv4Address::Parse(addr, ipAddr)) + return false; + + ep = FIPv4Endpoint(ipAddr, port); + return true; + } + }; + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Array helpers + ////////////////////////////////////////////////////////////////////////////////////////////// struct str + namespace arrays + { + // Max element in array + template + T max(const T* data, int size) + { + T result = data[0]; + for (int i = 1; i < size; i++) + if (result < data[i]) + result = data[i]; + return result; + } + + // Max element's index in array + template + size_t max_idx(const T* data, int size) + { + size_t idx = 0; + T result = data[0]; + for (int i = 1; i < size; i++) + if (result < data[i]) + { + result = data[i]; + idx = i; + } + return idx; + } + + // Min element in array + template + T min(const T* data, int size) + { + T result = data[0]; + for (int i = 1; i < size; i++) + if (result > data[i]) + result = data[i]; + return result; + } + + // Min element's index in array + template + size_t min_idx(const T* data, int size) + { + size_t idx = 0; + T result = data[0]; + for (int i = 1; i < size; i++) + if (result > data[i]) + { + result = data[i]; + idx = i; + } + return idx; + } + + // Helper for array size + template + constexpr size_t array_size(const T(&)[n]) + { + return n; + } + } + + + ////////////////////////////////////////////////////////////////////////////////////////////// + // Game helpers + ////////////////////////////////////////////////////////////////////////////////////////////// + namespace game + { + template + static void FindAllActors(UWorld* World, TArray& Out) + { + for (TActorIterator It(World, T::StaticClass()); It; ++It) + { + T* Actor = Cast(*It); + if (Actor && !Actor->IsPendingKill()) + { + Out.Add(Actor); + } + } + } + } +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterLog.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterLog.cpp new file mode 100644 index 000000000000..2337f8e5fe4b --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterLog.cpp @@ -0,0 +1,17 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterLog.h" + +// Plugin-wide log categories +DEFINE_LOG_CATEGORY(LogDisplayClusterGameMode); +DEFINE_LOG_CATEGORY(LogDisplayClusterEngine); +DEFINE_LOG_CATEGORY(LogDisplayClusterModule); +DEFINE_LOG_CATEGORY(LogDisplayClusterCluster); +DEFINE_LOG_CATEGORY(LogDisplayClusterConfig); +DEFINE_LOG_CATEGORY(LogDisplayClusterGame); +DEFINE_LOG_CATEGORY(LogDisplayClusterInput); +DEFINE_LOG_CATEGORY(LogDisplayClusterInputVRPN); +DEFINE_LOG_CATEGORY(LogDisplayClusterNetwork); +DEFINE_LOG_CATEGORY(LogDisplayClusterNetworkMsg); +DEFINE_LOG_CATEGORY(LogDisplayClusterRender); +DEFINE_LOG_CATEGORY(LogDisplayClusterBlueprint); diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterLog.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterLog.h new file mode 100644 index 000000000000..92810ea7c6df --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterLog.h @@ -0,0 +1,47 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +// Plugin-wide log categories +#if UE_BUILD_SHIPPING +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterGameMode, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterEngine, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterModule, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterCluster, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterConfig, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterGame, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterInput, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterInputVRPN, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterNetwork, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterNetworkMsg, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterRender, Warning, Warning); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterBlueprint, Warning, Warning); +#else +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterGameMode, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterEngine, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterModule, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterCluster, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterConfig, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterGame, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterInput, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterInputVRPN, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterNetwork, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterNetworkMsg, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterRender, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterBlueprint, Log, All); +#endif + + +//@todo: Linux@GCC will probably require other macro +#if UE_BUILD_SHIPPING + #define DISPLAY_CLUSTER_FUNC_TRACE(cat) ; +#else + #if PLATFORM_WINDOWS + #define DISPLAY_CLUSTER_FUNC_TRACE(cat) UE_LOG(cat, VeryVerbose, TEXT(">> %s"), TEXT(__FUNCTION__)) + //#define DISPLAY_CLUSTER_FUNC_TRACE(cat) UE_LOG(cat, VeryVerbose, TEXT(">> %s::%s::%d"), TEXT(__FILE__), TEXT(__FUNCTION__), __LINE__) + #else + #define DISPLAY_CLUSTER_FUNC_TRACE(cat) ; + #endif // PLATFORM_WINDOWS +#endif // UE_BUILD_SHIPPING diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterTypesConverter.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterTypesConverter.h new file mode 100644 index 000000000000..62b08c8b103a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Misc/DisplayClusterTypesConverter.h @@ -0,0 +1,62 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterOperationMode.h" +#include "DisplayClusterStrings.h" + + +/** + * Auxiliary class with different type conversion functions + */ +class FDisplayClusterTypesConverter +{ +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // TYPE --> STRING + ////////////////////////////////////////////////////////////////////////////////////////////// + template + static FString ToString(const ConvertFrom& from); + + template <> static FString ToString<> (const FString& from) { return from; } + template <> static FString ToString<> (const bool& from) { return (from ? DisplayClusterStrings::cfg::spec::ValTrue : DisplayClusterStrings::cfg::spec::ValFalse); } + template <> static FString ToString<> (const int32& from) { return FString::FromInt(from); } + template <> static FString ToString<> (const float& from) { return FString::SanitizeFloat(from); } + template <> static FString ToString<> (const double& from) { return FString::Printf(TEXT("%lf"), from); } + template <> static FString ToString<> (const FVector& from) { return from.ToString(); } + template <> static FString ToString<> (const FVector2D& from) { return from.ToString(); } + template <> static FString ToString<> (const FRotator& from) { return from.ToString(); } + + template <> static FString ToString<> (const EDisplayClusterOperationMode& from) + { + switch (from) + { + case EDisplayClusterOperationMode::Cluster: + return FString("cluster"); + case EDisplayClusterOperationMode::Standalone: + return FString("standalone"); + case EDisplayClusterOperationMode::Editor: + return FString("editor"); + case EDisplayClusterOperationMode::Disabled: + return FString("disabled"); + default: + return FString("unknown"); + } + } + + ////////////////////////////////////////////////////////////////////////////////////////////// + // STRING --> TYPE + ////////////////////////////////////////////////////////////////////////////////////////////// + template + static ConvertTo FromString(const FString& from); + + template <> static FString FromString<> (const FString& from) { return from; } + template <> static bool FromString<> (const FString& from) { return (from == FString("1") || from == DisplayClusterStrings::cfg::spec::ValTrue); } + template <> static int32 FromString<> (const FString& from) { return FCString::Atoi(*from); } + template <> static float FromString<> (const FString& from) { return FCString::Atof(*from); } + template <> static double FromString<> (const FString& from) { return FCString::Atod(*from); } + template <> static FVector FromString<> (const FString& from) { FVector vec; vec.InitFromString(from); return vec; } + template <> static FVector2D FromString<> (const FString& from) { FVector2D vec; vec.InitFromString(from); return vec; } + template <> static FRotator FromString<> (const FString& from) { FRotator rot; rot.InitFromString(from); return rot; } +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterClient.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterClient.cpp new file mode 100644 index 000000000000..034b658d2efe --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterClient.cpp @@ -0,0 +1,115 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterClient.h" +#include "Common/TcpSocketBuilder.h" + +#include "Misc/DisplayClusterAppExit.h" +#include "Misc/DisplayClusterLog.h" +#include "Misc/ScopeLock.h" + + +FDisplayClusterClient::FDisplayClusterClient(const FString& name) : + FDisplayClusterSocketOps(CreateSocket(name)), + Name(name) +{ +} + +FDisplayClusterClient::~FDisplayClusterClient() +{ + Disconnect(); +} + +bool FDisplayClusterClient::Connect(const FString& addr, const int32 port, const int32 triesAmount, const float delay) +{ + FScopeLock lock(&GetSyncObj()); + + // Generate IPv4 address + FIPv4Address ipAddr; + if (!FIPv4Address::Parse(addr, ipAddr)) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s couldn't parse the address [%s:%d]"), *Name, *addr, port); + return false; + } + + // Generate internet address + TSharedRef internetAddr = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); + internetAddr->SetIp(ipAddr.Value); + internetAddr->SetPort(port); + + // Start connection loop + int32 tryIdx = 0; + while(GetSocket()->Connect(*internetAddr) == false) + { + UE_LOG(LogDisplayClusterNetwork, Log, TEXT("%s couldn't connect to the server %s [%d]"), *Name, *(internetAddr->ToString(true)), tryIdx++); + if (triesAmount > 0 && tryIdx >= triesAmount) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s connection attempts limit reached"), *Name); + break; + } + + // Sleep some time before next try + FPlatformProcess::Sleep(delay); + } + + return IsOpen(); +} + +void FDisplayClusterClient::Disconnect() +{ + FScopeLock lock(&GetSyncObj()); + + UE_LOG(LogDisplayClusterNetwork, Log, TEXT("%s disconnecting..."), *Name); + + if (IsOpen()) + { + GetSocket()->Close(); + } +} + +FSocket* FDisplayClusterClient::CreateSocket(const FString& name, const int32 bufSize) +{ + FSocket* pSock = FTcpSocketBuilder(*name).AsBlocking().WithReceiveBufferSize(bufSize).WithSendBufferSize(bufSize); + check(pSock); + return pSock; +} + +bool FDisplayClusterClient::SendMsg(const FDisplayClusterMessage::Ptr& msg) +{ + const bool result = FDisplayClusterSocketOps::SendMsg(msg); + if (result == false) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Something wrong with connection (send). The cluster is inconsistent. Exit required.")); + } + + return result; +} + +FDisplayClusterMessage::Ptr FDisplayClusterClient::RecvMsg() +{ + FDisplayClusterMessage::Ptr response = FDisplayClusterSocketOps::RecvMsg(); + if (!response.IsValid()) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Something wrong with connection (recv). The cluster is inconsistent. Exit required.")); + } + + return response; +} + +FDisplayClusterMessage::Ptr FDisplayClusterClient::SendRecvMsg(const FDisplayClusterMessage::Ptr& msg) +{ + FDisplayClusterMessage::Ptr response; + + { + FScopeLock lock(&GetSyncObj()); + SendMsg(msg); + response = RecvMsg(); + } + + if (!response.IsValid()) + { + UE_LOG(LogDisplayClusterNetworkMsg, Warning, TEXT("No response")); + } + + return response; +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterClient.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterClient.h new file mode 100644 index 000000000000..96e01da11d92 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterClient.h @@ -0,0 +1,47 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterMessage.h" +#include "DisplayClusterSocketOps.h" + +#include "DisplayClusterConstants.h" + + +/** + * TCP client + */ +class FDisplayClusterClient + : protected FDisplayClusterSocketOps +{ +public: + FDisplayClusterClient(const FString& name); + virtual ~FDisplayClusterClient(); + +public: + // Connects to a server + bool Connect(const FString& addr, const int32 port, const int32 triesAmount = DisplayClusterConstants::net::ClientConnectTriesAmount, const float delay = DisplayClusterConstants::net::ClientConnectRetryDelay); + // Terminates current connection + void Disconnect(); + + virtual bool SendMsg(const FDisplayClusterMessage::Ptr& msg) override final; + virtual FDisplayClusterMessage::Ptr RecvMsg() override final; + + FDisplayClusterMessage::Ptr SendRecvMsg(const FDisplayClusterMessage::Ptr& msg); + + virtual FString GetName() const override final + { return Name; } + + inline bool IsConnected() const + { return IsOpen(); } + +protected: + // Creates client socket + FSocket* CreateSocket(const FString& name, const int32 bufSize = DisplayClusterConstants::net::SocketBufferSize); + +private: + // Client name + const FString Name; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterMessage.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterMessage.cpp new file mode 100644 index 000000000000..4e1a9f6f76ea --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterMessage.cpp @@ -0,0 +1,94 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterMessage.h" + +#include "Misc/DisplayClusterLog.h" + + +FDisplayClusterMessage::FDisplayClusterMessage() +{ +} + +FDisplayClusterMessage::FDisplayClusterMessage(const FString& name, const FString& type, const FString& protocol) : + Name(name), + Type(type), + Protocol(protocol) +{ +} + +FDisplayClusterMessage::~FDisplayClusterMessage() +{ +} + + +bool FDisplayClusterMessage::Serialize(FMemoryWriter& ar) +{ + // Header + ar << Name; + ar << Type; + ar << Protocol; + + TArray keys; + Arguments.GenerateKeyArray(keys); + + // Arguments amount + FString strArgAmount = FString::FromInt(Arguments.Num()); + ar << strArgAmount; + + // Arguments + for (int i = 0; i < keys.Num(); ++i) + { + ar << keys[i]; + ar << Arguments[keys[i]]; + } + + return true; +} + +bool FDisplayClusterMessage::Deserialize(FMemoryReader& ar) +{ + // Header + ar << Name; + ar << Type; + ar << Protocol; + + // Arguments amount + FString strArgsAmount; + ar << strArgsAmount; + const int32 amount = FCString::Atoi(*strArgsAmount); + check(amount >= 0); + + // Arguments + for (int32 i = 0; i < amount; ++i) + { + FString key; + FString val; + + ar << key; + ar << val; + + Arguments.Add(key, val); + } + + UE_LOG(LogDisplayClusterNetworkMsg, VeryVerbose, TEXT("Deserialized message: %s"), *ToString()); + + return true; +} + +FString FDisplayClusterMessage::ToString() const +{ + return FString::Printf(TEXT(""), *GetProtocol(), *GetType(), *GetName(), *ArgsToString()); +} + +FString FDisplayClusterMessage::ArgsToString() const +{ + FString str; + str.Reserve(512); + + for (auto it = Arguments.CreateConstIterator(); it; ++it) + { + str += FString::Printf(TEXT("%s=%s "), *it->Key, *it->Value); + } + + return str; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterMessage.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterMessage.h new file mode 100644 index 000000000000..7831123bf7d0 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterMessage.h @@ -0,0 +1,84 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDisplayClusterSerializable.h" + +#include "Serialization/MemoryReader.h" +#include "Serialization/MemoryWriter.h" + +#include "Misc/DisplayClusterTypesConverter.h" + + +/** + * Abstract network message + */ +class FDisplayClusterMessage + : IDisplayClusterSerializable +{ +public: + typedef TSharedPtr Ptr; + typedef TMap DataType; + +public: + FDisplayClusterMessage(); + FDisplayClusterMessage(const FString& name, const FString& type, const FString& protocol); + + FDisplayClusterMessage(const FDisplayClusterMessage&) = default; + FDisplayClusterMessage(FDisplayClusterMessage&&) = default; + + FDisplayClusterMessage& operator= (const FDisplayClusterMessage&) = default; + FDisplayClusterMessage& operator= (FDisplayClusterMessage&&) = default; + + virtual ~FDisplayClusterMessage(); + +public: + // Message head + inline FString GetName() const { return Name; } + inline FString GetType() const { return Type; } + inline FString GetProtocol() const { return Protocol; } + + // Sets arguments to a message + template + bool GetArg(const FString& argName, ValType& argVal) const + { + if (Arguments.Contains(argName)) + { + FString strVal = Arguments[argName]; + argVal = FDisplayClusterTypesConverter::FromString(strVal); + return true; + } + return false; + } + + // Get arguments from a message + template + void SetArg(const FString& argName, const ValType& argVal) + { + Arguments.Add(argName, FDisplayClusterTypesConverter::ToString(argVal)); + } + + // Get all arguments (be careful with the reference) + const DataType& GetArgs() const + { return Arguments; } + + void SetArgs(const DataType& data) + { Arguments = data; } + + // Serialization + virtual bool Serialize (FMemoryWriter& ar) override; + virtual bool Deserialize(FMemoryReader& ar) override; + + FString ToString() const; + +private: + //inline bool ExtractKeyVal(const FString& pair, FString& key, FString& val); + FString ArgsToString() const; + +private: + FString Name; + FString Type; + FString Protocol; + + DataType Arguments; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterServer.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterServer.cpp new file mode 100644 index 000000000000..c39650ca01f2 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterServer.cpp @@ -0,0 +1,106 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterServer.h" + +#include "Misc/DisplayClusterLog.h" +#include "Misc/ScopeLock.h" + + +FDisplayClusterServer::FDisplayClusterServer(const FString& name, const FString& addr, const int32 port) : + Name(name), + Address(addr), + Port(port), + Listener(name + FString("_listener")) +{ + check(port > 0 && port < 0xffff); + + // Bind connection handler method + Listener.OnConnectionAccepted().BindRaw(this, &FDisplayClusterServer::ConnectionHandler); +} + +FDisplayClusterServer::~FDisplayClusterServer() +{ + // Call from child .dtor + Shutdown(); +} + +bool FDisplayClusterServer::Start() +{ + FScopeLock lock(&InternalsSyncScope); + + if (bIsRunning == true) + { + return true; + } + + if (!Listener.StartListening(Address, Port)) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s couldn't start the listener [%s:%d]"), *Name, *Address, Port); + return false; + } + + // Update server state + bIsRunning = true; + + return bIsRunning; +} + +void FDisplayClusterServer::Shutdown() +{ + FScopeLock lock(&InternalsSyncScope); + + if (bIsRunning == false) + { + return; + } + + UE_LOG(LogDisplayClusterNetwork, Log, TEXT("%s stopping the service..."), *Name); + + // Stop connections listening + Listener.StopListening(); + // Destroy active sessions + Sessions.Reset(); + // Update server state + bIsRunning = false; +} + +bool FDisplayClusterServer::IsRunning() +{ + FScopeLock lock(&InternalsSyncScope); + return bIsRunning; +} + +bool FDisplayClusterServer::ConnectionHandler(FSocket* pSock, const FIPv4Endpoint& ep) +{ + FScopeLock lock(&InternalsSyncScope); + check(pSock); + + if (IsRunning() && IsConnectionAllowed(pSock, ep)) + { + pSock->SetLinger(false, 0); + pSock->SetNonBlocking(false); + + int32 newSize = static_cast(DisplayClusterConstants::net::SocketBufferSize); + int32 setSize; + pSock->SetReceiveBufferSize(newSize, setSize); + pSock->SetSendBufferSize(newSize, setSize); + + Sessions.Add(TUniquePtr(new FDisplayClusterSession(pSock, this, GetName() + FString("_session_") + ep.ToString()))); + return true; + } + + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterSessionListener +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterServer::NotifySessionOpen(FDisplayClusterSession* pSession) +{ +} + +void FDisplayClusterServer::NotifySessionClose(FDisplayClusterSession* pSession) +{ +} + + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterServer.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterServer.h new file mode 100644 index 000000000000..49c6108fa62c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterServer.h @@ -0,0 +1,79 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IDisplayClusterSessionListener.h" + +#include "DisplayClusterSession.h" +#include "DisplayClusterTcpListener.h" + + +struct FIPv4Endpoint; + + +/** + * TCP server + */ +class FDisplayClusterServer + : public IDisplayClusterSessionListener +{ +public: + FDisplayClusterServer(const FString& name, const FString& addr, const int32 port); + virtual ~FDisplayClusterServer(); + +public: + // Start server + virtual bool Start(); + // Stop server + virtual void Shutdown(); + + // Returns current server state + bool IsRunning(); + + // Server name + inline const FString& GetName() const + { return Name; } + + // Server addr + inline const FString& GetAddr() const + { return Address; } + + // Server port + inline const int32& GetPort() const + { return Port; } + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterSessionListener + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void NotifySessionOpen(FDisplayClusterSession* pSession) override; + virtual void NotifySessionClose(FDisplayClusterSession* pSession) override; + virtual FDisplayClusterMessage::Ptr ProcessMessage(FDisplayClusterMessage::Ptr msg) = 0; + +protected: + // Ask concrete server implementation if connection is allowed + virtual bool IsConnectionAllowed(FSocket* pSock, const FIPv4Endpoint& ep) + { return true; } + +private: + // Handles incoming connections + bool ConnectionHandler(FSocket* pSock, const FIPv4Endpoint& ep); + +private: + // Server data + const FString Name; + const FString Address; + const int32 Port; + + // Simple server state + bool bIsRunning = false; + // Socket listener + FDisplayClusterTcpListener Listener; + // Sync access + FCriticalSection InternalsSyncScope; + + // Active sessions + TArray> Sessions; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSession.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSession.cpp new file mode 100644 index 000000000000..1492dd9f58fc --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSession.cpp @@ -0,0 +1,68 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSession.h" +#include "DisplayClusterServer.h" +#include "DisplayClusterMessage.h" + +#include "HAL/RunnableThread.h" + +#include "Misc/DisplayClusterLog.h" + + +FDisplayClusterSession::FDisplayClusterSession(FSocket* pSock, IDisplayClusterSessionListener* pListener, const FString& name) : + FDisplayClusterSocketOps(pSock), + Listener(pListener), + Name(name) +{ + check(pSock); + check(pListener); + + ThreadObj = FRunnableThread::Create(this, *(Name + FString("_thread")), 128 * 1024, TPri_Normal, FPlatformAffinity::GetPoolThreadMask()); + ensure(ThreadObj); + + Listener->NotifySessionOpen(this); + + UE_LOG(LogDisplayClusterNetwork, Log, TEXT("Session %s started"), *Name); +} + +FDisplayClusterSession::~FDisplayClusterSession() +{ + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("Session %s .dtor"), *Name); + + Stop(); + ThreadObj->WaitForCompletion(); + delete ThreadObj; +} + +void FDisplayClusterSession::Stop() +{ + GetSocket()->Close(); +} + +uint32 FDisplayClusterSession::Run() +{ + UE_LOG(LogDisplayClusterNetwork, Log, TEXT("Session thread %s started"), *Name); + + while (IsOpen()) + { + FDisplayClusterMessage::Ptr req = RecvMsg(); + if (req.IsValid()) + { + FDisplayClusterMessage::Ptr resp = Listener->ProcessMessage(req); + if (resp.IsValid()) + { + if (SendMsg(resp)) + { + // 'Transaction' has been completed successfully so we continue the processing + continue; + } + } + } + + GetSocket()->Close(); + Listener->NotifySessionClose(this); + } + + UE_LOG(LogDisplayClusterNetwork, Log, TEXT("Session thread %s finished"), *Name); + return 0; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSession.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSession.h new file mode 100644 index 000000000000..2b4ff9af6095 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSession.h @@ -0,0 +1,35 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "HAL/Runnable.h" +#include "DisplayClusterSocketOps.h" + +#include "IDisplayClusterSessionListener.h" + + +/** + * TCP connection session + */ +class FDisplayClusterSession + : public FRunnable + , protected FDisplayClusterSocketOps +{ +public: + FDisplayClusterSession(FSocket* pSock, IDisplayClusterSessionListener* pListener, const FString& name = FString("DisplayClusterSession")); + ~FDisplayClusterSession(); + + virtual FString GetName() const override final + { return Name; } + +private: + virtual uint32 Run() override; + virtual void Stop() override; + +private: + const FString Name; + IDisplayClusterSessionListener* Listener = nullptr; + FRunnableThread* ThreadObj = nullptr; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSocketOps.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSocketOps.cpp new file mode 100644 index 000000000000..5f5ba80bc1dc --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSocketOps.cpp @@ -0,0 +1,194 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSocketOps.h" + +#include "DisplayClusterConstants.h" +#include "SocketSubsystem.h" + +#include "Misc/DisplayClusterLog.h" +#include "Misc/ScopeLock.h" + + +FDisplayClusterSocketOps::FDisplayClusterSocketOps(FSocket* pSock) : + Socket(pSock) +{ + DataBuffer.Reserve(DisplayClusterConstants::net::MessageBufferSize); +} + + +FDisplayClusterSocketOps::~FDisplayClusterSocketOps() +{ + ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(Socket); +} + +FDisplayClusterMessage::Ptr FDisplayClusterSocketOps::RecvMsg() +{ + FScopeLock lock(&GetSyncObj()); + + if (!IsOpen()) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s - not connected"), *GetName()); + return nullptr; + } + + // Read message header + if (!RecvChunk(sizeof(FDisplayClusterMessageHeader), DataBuffer, FString("header-chunk"))) + { + return nullptr; + } + + // Ok. Now we can extract header data + FDisplayClusterMessageHeader msgHeader; + FMemory::Memcpy(&msgHeader, DataBuffer.GetData(), sizeof(FDisplayClusterMessageHeader)); + + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("%s - message header received: %s"), *GetName(), *msgHeader.ToString()); + check(msgHeader.length > 0); + + // Read message body + if (!RecvChunk(msgHeader.length, DataBuffer, FString("body-chunk"))) + { + return nullptr; + } + + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("%s - message body received"), *GetName()); + + FDisplayClusterMessage::Ptr msg(new FDisplayClusterMessage()); + FMemoryReader ar = FMemoryReader(DataBuffer, false); + + // Deserialize message from buffer + if (!msg->Deserialize(ar)) + { + UE_LOG(LogDisplayClusterNetworkMsg, Error, TEXT("%s couldn't deserialize a message"), *GetName()); + return nullptr; + } + + // Succeeded + UE_LOG(LogDisplayClusterNetworkMsg, Verbose, TEXT("%s - received a message: %s"), *GetName(), *msg->ToString()); + return msg; +} + +bool FDisplayClusterSocketOps::RecvChunk(int32 chunkSize, TArray& chunkBuffer, const FString& chunkName) +{ + int32 bytesReadAll = 0; + int32 bytesReadNow = 0; + int32 bytesReadLeft = 0; + const int32 bytesAll = chunkSize; + chunkBuffer.Empty(DisplayClusterConstants::net::MessageBufferSize); + + // Receive message header at first + while (bytesReadAll < bytesAll) + { + // Read data + bytesReadLeft = bytesAll - bytesReadAll; + if (!Socket->Recv(chunkBuffer.GetData(), bytesReadLeft, bytesReadNow)) + { + UE_LOG(LogDisplayClusterNetwork, Warning, TEXT("%s - %s recv failed - socket error. Cluster integrity disturbed."), *GetName(), *chunkName); + return false; + } + + // Check amount of read data + if (bytesReadNow <= 0 || bytesReadNow > bytesReadLeft) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s - %s recv failed - read wrong amount of bytes: %d"), *GetName(), *chunkName, bytesReadNow); + return false; + } + + bytesReadAll += bytesReadNow; + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("%s - %s received %d bytes, left %d bytes"), *GetName(), *chunkName, bytesReadNow, bytesAll - bytesReadAll); + + // Convergence check + if (bytesReadAll > bytesAll) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s - %s convergence fail: overall received %d of %d"), *GetName(), *chunkName, bytesReadAll, bytesAll); + return false; + } + } + + // Update array length (amount of bytes as array elements) + chunkBuffer.SetNumUninitialized(bytesReadAll); + + // Operation succeeded + return true; +} + +bool FDisplayClusterSocketOps::SendMsg(const FDisplayClusterMessage::Ptr& msg) +{ + FScopeLock lock(&GetSyncObj()); + + UE_LOG(LogDisplayClusterNetwork, Verbose, TEXT("%s - sending message: %s"), *GetName(), *msg->ToString()); + + if (!IsOpen()) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s not connected"), *GetName()); + return false; + } + + // Prepare output buffer + DataBuffer.Empty(DisplayClusterConstants::net::MessageBufferSize); + DataBuffer.AddZeroed(sizeof(FDisplayClusterMessageHeader)); + FMemoryWriter memoryWriter(DataBuffer); + + // Reserve space for message header + memoryWriter.Seek(sizeof(FDisplayClusterMessageHeader)); + + // Serialize the message + if (!msg->Serialize(memoryWriter)) + { + UE_LOG(LogDisplayClusterNetworkMsg, Error, TEXT("%s couldn't serialize a message"), *GetName()); + return false; + } + + // Check bounds + const int32 msgLength = DataBuffer.Num(); + if (msgLength > DisplayClusterConstants::net::SocketBufferSize) + { + UE_LOG(LogDisplayClusterNetworkMsg, Error, TEXT("Outgoing message length exceeds buffer limit: length=%d > limit=%d"), msgLength, DisplayClusterConstants::net::SocketBufferSize); + return false; + } + + // Initialize message header + FDisplayClusterMessageHeader msgHeader; + msgHeader.length = static_cast(msgLength & 0x7FFF) - sizeof(FDisplayClusterMessageHeader); + UE_LOG(LogDisplayClusterNetworkMsg, Verbose, TEXT("Outgoing message body length %d"), msgHeader.length); + + // Fill packet header with message data length + FMemory::Memcpy(DataBuffer.GetData(), &msgHeader, sizeof(FDisplayClusterMessageHeader)); + + int32 bytesWriteAll = 0; + int32 bytesWriteNow = 0; + int32 bytesWriteLeft = 0; + + while (bytesWriteAll < msgLength) + { + bytesWriteLeft = msgLength - bytesWriteAll; + + // Send data + if (!Socket->Send(DataBuffer.GetData() + bytesWriteAll, bytesWriteLeft, bytesWriteNow)) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s - couldn't send a message (length=%d)"), *GetName(), msgLength); + return false; + } + + // Check amount of sent bytes + if (bytesWriteNow <= 0 || bytesWriteNow > bytesWriteLeft) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s - sent wrong amount of bytes: %d of %d left"), *GetName(), bytesWriteNow, bytesWriteLeft); + return false; + } + + bytesWriteAll += bytesWriteNow; + UE_LOG(LogDisplayClusterNetwork, VeryVerbose, TEXT("%s - sent %d bytes, left %d bytes"), *GetName(), bytesWriteNow, msgLength - bytesWriteAll); + + // Convergence check + if (bytesWriteAll > msgLength) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("%s - convergence failed: overall sent %d of %d"), *GetName(), bytesWriteAll, msgLength); + return false; + } + } + + UE_LOG(LogDisplayClusterNetwork, Verbose, TEXT("%s - message sent"), *GetName()); + + return true; +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSocketOps.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSocketOps.h new file mode 100644 index 000000000000..f8cfe3ebd753 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterSocketOps.h @@ -0,0 +1,58 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Sockets.h" +#include "DisplayClusterMessage.h" + + +/** + * Socket operations (base class for client and server) + */ +class FDisplayClusterSocketOps +{ +public: + FDisplayClusterSocketOps(FSocket* pSock); + virtual ~FDisplayClusterSocketOps(); + +public: + virtual bool SendMsg(const FDisplayClusterMessage::Ptr& msg); + virtual FDisplayClusterMessage::Ptr RecvMsg(); + + inline FSocket* GetSocket() const + { return Socket; } + + inline bool IsOpen() const + { return (Socket && (Socket->GetConnectionState() == ESocketConnectionState::SCS_Connected)); } + + // Provides with net unit name + virtual FString GetName() const = 0; + +protected: + // Provides with a synchronization object for underlying operations (message send/recv) + inline FCriticalSection& GetSyncObj() const + { return InternalsSyncScope; } + +private: + bool RecvChunk(int32 chunkSize, TArray& chunkBuffer, const FString& chunkName = FString("DataChunk")); + +private: + struct FDisplayClusterMessageHeader + { + int16 length; + + FString ToString() + { return FString::Printf(TEXT(""), length); } + + }; + +private: + // Socket + FSocket* Socket = nullptr; + // Data buffer for incoming and outgoing messages + TArray DataBuffer; + // Access sync object + mutable FCriticalSection InternalsSyncScope; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterTcpListener.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterTcpListener.cpp new file mode 100644 index 000000000000..f7bc1d3ffe8c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterTcpListener.cpp @@ -0,0 +1,153 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterTcpListener.h" + +#include "Misc/DisplayClusterLog.h" +#include "HAL/RunnableThread.h" + +#include "Common/TcpSocketBuilder.h" + +#include "Misc/DisplayClusterAppExit.h" +#include "Misc/DisplayClusterHelpers.h" + + +FDisplayClusterTcpListener::FDisplayClusterTcpListener(const FString& name) : + Name(name) +{ +} + + +FDisplayClusterTcpListener::~FDisplayClusterTcpListener() +{ + // Just free resources by stopping the listening + StopListening(); +} + + +bool FDisplayClusterTcpListener::StartListening(const FString& addr, const int32 port) +{ + FScopeLock lock(&InternalsSyncScope); + + if (bIsListening == true) + { + return true; + } + + FIPv4Endpoint ep; + if (!DisplayClusterHelpers::net::GenIPv4Endpoint(addr, port, ep)) + { + return false; + } + + return StartListening(ep); +} + +bool FDisplayClusterTcpListener::StartListening(const FIPv4Endpoint& ep) +{ + FScopeLock lock(&InternalsSyncScope); + + if (bIsListening == true) + { + return true; + } + + // Save new endpoint + Endpoint = ep; + + // Create listening thread + ThreadObj = FRunnableThread::Create(this, *(Name + FString("_thread")), 1 * 1024, TPri_Normal); + ensure(ThreadObj); + + // Update state + bIsListening = true; + + return bIsListening; +} + + +void FDisplayClusterTcpListener::StopListening() +{ + FScopeLock lock(&InternalsSyncScope); + + if (bIsListening == false) + { + return; + } + + // Ask runnable to stop + Stop(); + + // Wait for thread finish and release it then + if (ThreadObj) + { + ThreadObj->WaitForCompletion(); + delete ThreadObj; + ThreadObj = nullptr; + } +} + +bool FDisplayClusterTcpListener::IsActive() const +{ + return bIsListening; +} + +bool FDisplayClusterTcpListener::Init() +{ + // Create socket + SocketObj = FTcpSocketBuilder(*Name).AsBlocking().BoundToEndpoint(Endpoint).Listening(128); + if (!SocketObj) + { + // Just exit. No need to perform some notification from this thread to the cluster manager to notify + // about this fail. Just kill the application. + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::KillImmediately, FString("Couldn't start listener socket")); + return false; + } + + return true; +} + +uint32 FDisplayClusterTcpListener::Run() +{ + TSharedRef RemoteAddress = ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->CreateInternetAddr(); + + if (SocketObj) + { + while (FSocket* pSock = SocketObj->Accept(*RemoteAddress, TEXT("FDisplayClusterTcpListener client"))) + { + if (OnConnectionAcceptedDelegate.IsBound()) + { + if (!OnConnectionAcceptedDelegate.Execute(pSock, FIPv4Endpoint(RemoteAddress))) + { + pSock->Close(); + ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(pSock); + } + } + } + } + else + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("Socket is not initialized")); + return 0; + } + + return 0; +} + +void FDisplayClusterTcpListener::Stop() +{ + // Close the socket to unblock thread + if (SocketObj) + { + SocketObj->Close(); + } +} + +void FDisplayClusterTcpListener::Exit() +{ + // Release the socket + if (SocketObj) + { + ISocketSubsystem::Get(PLATFORM_SOCKETSUBSYSTEM)->DestroySocket(SocketObj); + SocketObj = nullptr; + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterTcpListener.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterTcpListener.h new file mode 100644 index 000000000000..9e674ffd7ddc --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/DisplayClusterTcpListener.h @@ -0,0 +1,67 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Sockets.h" +#include "HAL/Runnable.h" +#include "Delegates/DelegateCombinations.h" +#include "Interfaces/IPv4/IPv4Endpoint.h" +#include "DisplayClusterConstants.h" + + +/** + * TCP connection listener + */ +class FDisplayClusterTcpListener + : public FRunnable +{ +public: + DECLARE_DELEGATE_RetVal_TwoParams(bool, TOnConnectionAcceptedDelegate, FSocket*, const FIPv4Endpoint&) + +public: + FDisplayClusterTcpListener(const FString& name); + ~FDisplayClusterTcpListener(); + +public: + + bool StartListening(const FString& addr, const int32 port); + bool StartListening(const FIPv4Endpoint& ep); + void StopListening(); + + bool IsActive() const; + + inline TOnConnectionAcceptedDelegate& OnConnectionAccepted() + { return OnConnectionAcceptedDelegate; } + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // FRunnable + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Init() override; + virtual uint32 Run() override; + virtual void Stop() override; + virtual void Exit() override; + +private: + // Creates server socket + FSocket* CreateSocket(const FString& name, const FString& addr, const int32 port, const int32 bufSize = DisplayClusterConstants::net::SocketBufferSize); + +private: + // Socket name + FString Name; + // Listening socket + FSocket* SocketObj = nullptr; + // Listening endpoint + FIPv4Endpoint Endpoint; + // Holds the thread object + FRunnableThread* ThreadObj; + // Sync access + FCriticalSection InternalsSyncScope; + // Listening state + bool bIsListening = false; + +private: + // Holds a delegate to be invoked when an incoming connection has been accepted. + TOnConnectionAcceptedDelegate OnConnectionAcceptedDelegate; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/IDisplayClusterSessionListener.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/IDisplayClusterSessionListener.h new file mode 100644 index 000000000000..28ee44655489 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/IDisplayClusterSessionListener.h @@ -0,0 +1,27 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterMessage.h" + +class FDisplayClusterSession; + + +/** + * TCP session listener interface + */ +struct IDisplayClusterSessionListener +{ + virtual ~IDisplayClusterSessionListener() + { } + + virtual void NotifySessionOpen(FDisplayClusterSession* pSession) + { } + + virtual void NotifySessionClose(FDisplayClusterSession* pSession) + { } + + // Pass a message to a concrete implementation + virtual FDisplayClusterMessage::Ptr ProcessMessage(FDisplayClusterMessage::Ptr msg) = 0; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Protocol/IPDisplayClusterClusterSyncProtocol.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Protocol/IPDisplayClusterClusterSyncProtocol.h new file mode 100644 index 000000000000..02cbbe91e1f7 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Protocol/IPDisplayClusterClusterSyncProtocol.h @@ -0,0 +1,35 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Network/DisplayClusterMessage.h" + + +/** + * Cluster state synchronization protocol + */ +class IPDisplayClusterClusterSyncProtocol +{ +public: + // Game start barrier + virtual void WaitForGameStart() = 0; + + // Frame start barrier + virtual void WaitForFrameStart() = 0; + + // Frame end barrier + virtual void WaitForFrameEnd() = 0; + + // Tick end barrier + virtual void WaitForTickEnd() = 0; + + // Provides with time delta for current frame + virtual void GetDeltaTime(float& deltaTime) = 0; + + // Sync objects + virtual void GetSyncData(FDisplayClusterMessage::DataType& data) = 0; + + // Sync input + virtual void GetInputData(FDisplayClusterMessage::DataType& data) = 0; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Protocol/IPDisplayClusterSwapSyncProtocol.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Protocol/IPDisplayClusterSwapSyncProtocol.h new file mode 100644 index 000000000000..d31a1d03b5c9 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Protocol/IPDisplayClusterSwapSyncProtocol.h @@ -0,0 +1,15 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + + +/** + * Swap synchronization protocol + */ +class IPDisplayClusterSwapSyncProtocol +{ +public: + // Swap sync barrier + virtual void WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) = 0; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncClient.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncClient.cpp new file mode 100644 index 000000000000..2e6ef3ef3984 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncClient.cpp @@ -0,0 +1,104 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterClusterSyncClient.h" +#include "DisplayClusterClusterSyncMsg.h" + +#include "Misc/DisplayClusterLog.h" +#include "Misc/ScopeLock.h" + + +FDisplayClusterClusterSyncClient::FDisplayClusterClusterSyncClient() : + FDisplayClusterClient(FString("CLN_CS")) +{ +} + +FDisplayClusterClusterSyncClient::FDisplayClusterClusterSyncClient(const FString& name) : + FDisplayClusterClient(name) +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterClusterSyncProtocol +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterClusterSyncClient::WaitForGameStart() +{ + static TSharedPtr request(new FDisplayClusterMessage(FDisplayClusterClusterSyncMsg::WaitForGameStart::name, FDisplayClusterClusterSyncMsg::TypeRequest, FDisplayClusterClusterSyncMsg::ProtocolName)); + TSharedPtr response; + + { + FScopeLock lock(&GetSyncObj()); + SendMsg(request); + response = RecvMsg(); + } + + if (!response.IsValid()) + { + UE_LOG(LogDisplayClusterNetworkMsg, Warning, TEXT("No response")); + return; + } +} + +void FDisplayClusterClusterSyncClient::WaitForFrameStart() +{ + static const TSharedPtr request(new FDisplayClusterMessage(FDisplayClusterClusterSyncMsg::WaitForFrameStart::name, FDisplayClusterClusterSyncMsg::TypeRequest, FDisplayClusterClusterSyncMsg::ProtocolName)); + TSharedPtr response = SendRecvMsg(request); +} + +void FDisplayClusterClusterSyncClient::WaitForFrameEnd() +{ + static const TSharedPtr request(new FDisplayClusterMessage(FDisplayClusterClusterSyncMsg::WaitForFrameEnd::name, FDisplayClusterClusterSyncMsg::TypeRequest, FDisplayClusterClusterSyncMsg::ProtocolName)); + TSharedPtr response = SendRecvMsg(request); +} + +void FDisplayClusterClusterSyncClient::WaitForTickEnd() +{ + static const TSharedPtr request(new FDisplayClusterMessage(FDisplayClusterClusterSyncMsg::WaitForTickEnd::name, FDisplayClusterClusterSyncMsg::TypeRequest, FDisplayClusterClusterSyncMsg::ProtocolName)); + TSharedPtr response = SendRecvMsg(request); +} + +void FDisplayClusterClusterSyncClient::GetDeltaTime(float& deltaTime) +{ + static const TSharedPtr request(new FDisplayClusterMessage(FDisplayClusterClusterSyncMsg::GetDeltaTime::name, FDisplayClusterClusterSyncMsg::TypeRequest, FDisplayClusterClusterSyncMsg::ProtocolName)); + TSharedPtr response = SendRecvMsg(request); + + if (!response.IsValid()) + { + return; + } + + // Extract sync data from response message + if (response->GetArg(FDisplayClusterClusterSyncMsg::GetDeltaTime::argDeltaTime, deltaTime) == false) + { + UE_LOG(LogDisplayClusterNetworkMsg, Error, TEXT("Coulnd't extract an argument: %s"), FDisplayClusterClusterSyncMsg::GetDeltaTime::argDeltaTime); + } +} + +void FDisplayClusterClusterSyncClient::GetSyncData(FDisplayClusterMessage::DataType& data) +{ + static const TSharedPtr request(new FDisplayClusterMessage(FDisplayClusterClusterSyncMsg::GetSyncData::name, FDisplayClusterClusterSyncMsg::TypeRequest, FDisplayClusterClusterSyncMsg::ProtocolName)); + TSharedPtr response = SendRecvMsg(request); + + if (!response.IsValid()) + { + return; + } + + // Extract sync data from response message + data = response->GetArgs(); +} + +void FDisplayClusterClusterSyncClient::GetInputData(FDisplayClusterMessage::DataType& data) +{ + static const TSharedPtr request(new FDisplayClusterMessage(FDisplayClusterClusterSyncMsg::GetInputData::name, FDisplayClusterClusterSyncMsg::TypeRequest, FDisplayClusterClusterSyncMsg::ProtocolName)); + TSharedPtr response = SendRecvMsg(request); + + if (!response.IsValid()) + { + return; + } + + // Extract sync data from response message + data = response->GetArgs(); +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncClient.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncClient.h new file mode 100644 index 000000000000..87b837ca590c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncClient.h @@ -0,0 +1,33 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Network/DisplayClusterClient.h" +#include "Network/DisplayClusterMessage.h" +#include "Network/Protocol/IPDisplayClusterClusterSyncProtocol.h" + + +/** + * Cluster synchronization client + */ +class FDisplayClusterClusterSyncClient + : public FDisplayClusterClient + , public IPDisplayClusterClusterSyncProtocol +{ +public: + FDisplayClusterClusterSyncClient(); + FDisplayClusterClusterSyncClient(const FString& name); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterClusterSyncProtocol + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void WaitForGameStart() override; + virtual void WaitForFrameStart() override; + virtual void WaitForFrameEnd() override; + virtual void WaitForTickEnd() override; + virtual void GetDeltaTime(float& deltaTime) override; + virtual void GetSyncData(FDisplayClusterMessage::DataType& data) override; + virtual void GetInputData(FDisplayClusterMessage::DataType& data) override; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncMsg.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncMsg.h new file mode 100644 index 000000000000..470913f07c5e --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncMsg.h @@ -0,0 +1,52 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + + +/** + * Cluster synchronization messages + */ +//@todo: encapsulate strings below in message classes +namespace FDisplayClusterClusterSyncMsg +{ + constexpr static auto ProtocolName = "ClusterSync"; + + constexpr static auto TypeRequest = "request"; + constexpr static auto TypeResponse = "response"; + + namespace WaitForGameStart + { + constexpr static auto name = "WaitForGameStart"; + }; + + namespace WaitForFrameStart + { + constexpr static auto name = "WaitForFrameStart"; + }; + + namespace WaitForFrameEnd + { + constexpr static auto name = "WaitForFrameEnd"; + }; + + namespace WaitForTickEnd + { + constexpr static auto name = "WaitForTickEnd"; + }; + + namespace GetDeltaTime + { + constexpr static auto name = "GetDeltaTime"; + constexpr static auto argDeltaTime = "DeltaTime"; + }; + + namespace GetSyncData + { + constexpr static auto name = "GetSyncData"; + }; + + namespace GetInputData + { + constexpr static auto name = "GetInputData"; + } +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncService.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncService.cpp new file mode 100644 index 000000000000..69a4d6b7778b --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncService.cpp @@ -0,0 +1,191 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterClusterSyncService.h" +#include "DisplayClusterClusterSyncMsg.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Input/IPDisplayClusterInputManager.h" + +#include "Misc/DisplayClusterAppExit.h" +#include "Misc/DisplayClusterLog.h" + +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +FDisplayClusterClusterSyncService::FDisplayClusterClusterSyncService(const FString& addr, const int32 port) : + FDisplayClusterService(FString("SRV_CS"), addr, port), + BarrierGameStart (GDisplayCluster->GetPrivateClusterMgr()->GetNodesAmount(), FString("GameStart_barrier"), DisplayClusterConstants::net::BarrierGameStartWaitTimeout), + BarrierFrameStart (GDisplayCluster->GetPrivateClusterMgr()->GetNodesAmount(), FString("FrameStart_barrier"), DisplayClusterConstants::net::BarrierWaitTimeout), + BarrierFrameEnd (GDisplayCluster->GetPrivateClusterMgr()->GetNodesAmount(), FString("FrameEnd_barrier"), DisplayClusterConstants::net::BarrierWaitTimeout), + BarrierTickEnd (GDisplayCluster->GetPrivateClusterMgr()->GetNodesAmount(), FString("TickEnd_barrier"), DisplayClusterConstants::net::BarrierWaitTimeout) +{ +} + +FDisplayClusterClusterSyncService::~FDisplayClusterClusterSyncService() +{ + Shutdown(); +} + + +bool FDisplayClusterClusterSyncService::Start() +{ + BarrierGameStart.Activate(); + BarrierFrameStart.Activate(); + BarrierFrameEnd.Activate(); + BarrierTickEnd.Activate(); + + return FDisplayClusterServer::Start(); +} + +void FDisplayClusterClusterSyncService::Shutdown() +{ + BarrierGameStart.Deactivate(); + BarrierFrameStart.Deactivate(); + BarrierFrameEnd.Deactivate(); + BarrierTickEnd.Deactivate(); + + return FDisplayClusterServer::Shutdown(); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterSessionListener +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterClusterSyncService::NotifySessionOpen(FDisplayClusterSession* pSession) +{ + FDisplayClusterService::NotifySessionOpen(pSession); +} + +void FDisplayClusterClusterSyncService::NotifySessionClose(FDisplayClusterSession* pSession) +{ + // Unblock waiting threads to allow current Tick() finish + BarrierGameStart.Deactivate(); + BarrierFrameStart.Deactivate(); + BarrierFrameEnd.Deactivate(); + BarrierTickEnd.Deactivate(); + + FDisplayClusterService::NotifySessionClose(pSession); +} + +FDisplayClusterMessage::Ptr FDisplayClusterClusterSyncService::ProcessMessage(FDisplayClusterMessage::Ptr msg) +{ + // Check the pointer + if (msg.IsValid() == false) + { + UE_LOG(LogDisplayClusterNetworkMsg, Error, TEXT("%s - Couldn't process the message"), *GetName()); + return nullptr; + } + + UE_LOG(LogDisplayClusterNetwork, Verbose, TEXT("%s - Processing message %s"), *GetName(), *msg->ToString()); + + // Check protocol and type + if (msg->GetProtocol() != FDisplayClusterClusterSyncMsg::ProtocolName || msg->GetType() != FDisplayClusterClusterSyncMsg::TypeRequest) + { + UE_LOG(LogDisplayClusterNetworkMsg, Error, TEXT("%s - Unsupported message type: %s"), *GetName(), *msg->ToString()); + return nullptr; + } + + // Initialize response message + FDisplayClusterMessage::Ptr response = FDisplayClusterMessage::Ptr(new FDisplayClusterMessage(msg->GetName(), FDisplayClusterClusterSyncMsg::TypeResponse, msg->GetProtocol())); + + // Dispatch the message + const FString msgName = msg->GetName(); + if (msgName == FDisplayClusterClusterSyncMsg::WaitForGameStart::name) + { + WaitForGameStart(); + return response; + } + else if (msgName == FDisplayClusterClusterSyncMsg::WaitForFrameStart::name) + { + WaitForFrameStart(); + return response; + } + else if (msgName == FDisplayClusterClusterSyncMsg::WaitForFrameEnd::name) + { + WaitForFrameEnd(); + return response; + } + else if (msgName == FDisplayClusterClusterSyncMsg::WaitForTickEnd::name) + { + WaitForTickEnd(); + return response; + } + else if (msgName == FDisplayClusterClusterSyncMsg::GetDeltaTime::name) + { + float deltaTime = 0.0f; + GetDeltaTime(deltaTime); + response->SetArg(FDisplayClusterClusterSyncMsg::GetDeltaTime::argDeltaTime, deltaTime); + return response; + } + else if (msgName == FDisplayClusterClusterSyncMsg::GetSyncData::name) + { + FDisplayClusterMessage::DataType data; + GetSyncData(data); + + response->SetArgs(data); + return response; + } + else if (msgName == FDisplayClusterClusterSyncMsg::GetInputData::name) + { + FDisplayClusterMessage::DataType data; + GetInputData(data); + + response->SetArgs(data); + return response; + } + + // Being here means that we have no appropriate dispatch logic for this message + UE_LOG(LogDisplayClusterNetworkMsg, Warning, TEXT("%s - A dispatcher for this message hasn't been implemented yet <%s>"), *GetName(), *msg->ToString()); + return nullptr; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterClusterSyncProtocol +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterClusterSyncService::WaitForGameStart() +{ + if (BarrierGameStart.Wait() != FDisplayClusterBarrier::WaitResult::Ok) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Error on game start barrier. Exit required.")); + } +} + +void FDisplayClusterClusterSyncService::WaitForFrameStart() +{ + if (BarrierFrameStart.Wait() != FDisplayClusterBarrier::WaitResult::Ok) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Error on frame start barrier. Exit required.")); + } +} + +void FDisplayClusterClusterSyncService::WaitForFrameEnd() +{ + if (BarrierFrameEnd.Wait() != FDisplayClusterBarrier::WaitResult::Ok) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Error on frame end barrier. Exit required.")); + } +} + +void FDisplayClusterClusterSyncService::WaitForTickEnd() +{ + if (BarrierTickEnd.Wait() != FDisplayClusterBarrier::WaitResult::Ok) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Error on tick end barrier. Exit required.")); + } +} + +void FDisplayClusterClusterSyncService::GetDeltaTime(float& deltaTime) +{ + deltaTime = GDisplayCluster->GetPrivateClusterMgr()->GetDeltaTime(); +} + +void FDisplayClusterClusterSyncService::GetSyncData(FDisplayClusterMessage::DataType& data) +{ + GDisplayCluster->GetPrivateClusterMgr()->ExportSyncData(data); +} + +void FDisplayClusterClusterSyncService::GetInputData(FDisplayClusterMessage::DataType& data) +{ + GDisplayCluster->GetPrivateInputMgr()->ExportInputData(data); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncService.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncService.h new file mode 100644 index 000000000000..cd1190a4e9a7 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/ClusterSync/DisplayClusterClusterSyncService.h @@ -0,0 +1,58 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/DisplayClusterBarrier.h" +#include "Network/DisplayClusterMessage.h" +#include "Network/Service/DisplayClusterService.h" +#include "Network/Protocol/IPDisplayClusterClusterSyncProtocol.h" + + + +/** + * Cluster synchronization server + */ +class FDisplayClusterClusterSyncService + : public FDisplayClusterService + , private IPDisplayClusterClusterSyncProtocol +{ +public: + FDisplayClusterClusterSyncService(const FString& addr, const int32 port); + virtual ~FDisplayClusterClusterSyncService(); + +public: + virtual bool Start() override; + void Shutdown() override; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterSessionListener + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void NotifySessionOpen(FDisplayClusterSession* pSession) override; + virtual void NotifySessionClose(FDisplayClusterSession* pSession) override; + virtual FDisplayClusterMessage::Ptr ProcessMessage(FDisplayClusterMessage::Ptr msg) override; + +private: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterClusterSyncProtocol + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void WaitForGameStart() override; + virtual void WaitForFrameStart() override; + virtual void WaitForFrameEnd() override; + virtual void WaitForTickEnd() override; + virtual void GetDeltaTime(float& deltaTime) override; + virtual void GetSyncData(FDisplayClusterMessage::DataType& data) override; + virtual void GetInputData(FDisplayClusterMessage::DataType& data) override; + +private: + // Game start sync barrier + FDisplayClusterBarrier BarrierGameStart; + // Frame start barrier + FDisplayClusterBarrier BarrierFrameStart; + // Frame end barrier + FDisplayClusterBarrier BarrierFrameEnd; + // Tick end barrier + FDisplayClusterBarrier BarrierTickEnd; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/DisplayClusterService.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/DisplayClusterService.cpp new file mode 100644 index 000000000000..04abd3c58883 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/DisplayClusterService.cpp @@ -0,0 +1,50 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterService.h" +#include "Network/DisplayClusterSession.h" + +#include "Config/IPDisplayClusterConfigManager.h" +#include "Config/DisplayClusterConfigTypes.h" + +#include "Misc/DisplayClusterAppExit.h" +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +FDisplayClusterService::FDisplayClusterService(const FString& name, const FString& addr, const int32 port) : + FDisplayClusterServer(name, addr, port) +{ +} + +bool FDisplayClusterService::IsClusterIP(const FIPv4Endpoint& ep) +{ + TArray nodes = GDisplayCluster->GetPrivateConfigMgr()->GetClusterNodes(); + const FString addr = ep.Address.ToString(); + + return nullptr != nodes.FindByPredicate([addr](const FDisplayClusterConfigClusterNode& node) + { + return addr == node.Addr; + }); +} + +bool FDisplayClusterService::IsConnectionAllowed(FSocket* pSock, const FIPv4Endpoint& ep) +{ + // By default any DisplayCluster service must be within a cluster + return FDisplayClusterService::IsClusterIP(ep); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterSessionListener +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterService::NotifySessionOpen(FDisplayClusterSession* pSession) +{ + FDisplayClusterServer::NotifySessionOpen(pSession); +} + +void FDisplayClusterService::NotifySessionClose(FDisplayClusterSession* pSession) +{ + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, GetName() + FString(" - Connection interrupted. Application exit requested.")); + FDisplayClusterServer::NotifySessionClose(pSession); +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/DisplayClusterService.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/DisplayClusterService.h new file mode 100644 index 000000000000..5c4950b9eb42 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/DisplayClusterService.h @@ -0,0 +1,34 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Network/DisplayClusterServer.h" +#include "Sockets.h" + +class FDisplayClusterSession; +struct FIPv4Endpoint; + + +/** + * Abstract DisplayCluster server + */ +class FDisplayClusterService + : public FDisplayClusterServer +{ +public: + FDisplayClusterService(const FString& name, const FString& addr, const int32 port); + +public: + static bool IsClusterIP(const FIPv4Endpoint& ep); + +protected: + virtual bool IsConnectionAllowed(FSocket* pSock, const FIPv4Endpoint& ep) override; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterSessionListener + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void NotifySessionOpen(FDisplayClusterSession* pSession) override; + virtual void NotifySessionClose(FDisplayClusterSession* pSession) override; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncClient.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncClient.cpp new file mode 100644 index 000000000000..0193cda18194 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncClient.cpp @@ -0,0 +1,47 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSwapSyncClient.h" +#include "DisplayClusterSwapSyncMsg.h" + +#include "Misc/DisplayClusterLog.h" + + +FDisplayClusterSwapSyncClient::FDisplayClusterSwapSyncClient() : + FDisplayClusterClient(FString("CLN_SS")) +{ +} + +FDisplayClusterSwapSyncClient::FDisplayClusterSwapSyncClient(const FString& name) : + FDisplayClusterClient(name) +{ +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterSwapSyncProtocol +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterSwapSyncClient::WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) +{ + static const TSharedPtr request(new FDisplayClusterMessage(FDisplayClusterSwapSyncMsg::WaitForSwapSync::name, FDisplayClusterSwapSyncMsg::TypeRequest, FDisplayClusterSwapSyncMsg::ProtocolName)); + TSharedPtr response = SendRecvMsg(request); + + if (response.IsValid()) + { + if (pThreadWaitTime) + { + if (!response->GetArg(FString(FDisplayClusterSwapSyncMsg::WaitForSwapSync::argThreadTime), *pThreadWaitTime)) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("Argument %s not available"), FDisplayClusterSwapSyncMsg::WaitForSwapSync::argThreadTime); + } + } + + if (pBarrierWaitTime) + { + if (!response->GetArg(FString(FDisplayClusterSwapSyncMsg::WaitForSwapSync::argBarrierTime), *pBarrierWaitTime)) + { + UE_LOG(LogDisplayClusterNetwork, Error, TEXT("Argument %s not available"), FDisplayClusterSwapSyncMsg::WaitForSwapSync::argBarrierTime); + } + } + } +} + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncClient.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncClient.h new file mode 100644 index 000000000000..467819b90689 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncClient.h @@ -0,0 +1,26 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Network/DisplayClusterClient.h" +#include "Network/Protocol/IPDisplayClusterSwapSyncProtocol.h" + + +/** + * Swap synchronization client + */ +class FDisplayClusterSwapSyncClient + : public FDisplayClusterClient + , public IPDisplayClusterSwapSyncProtocol +{ +public: + FDisplayClusterSwapSyncClient(); + FDisplayClusterSwapSyncClient(const FString& name); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterSwapSyncProtocol + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) override; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncMsg.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncMsg.h new file mode 100644 index 000000000000..7675dc7852c2 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncMsg.h @@ -0,0 +1,23 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + + +/** + * Swap synchronization messages + */ +//@todo: encapsulate strings below in message classes +struct FDisplayClusterSwapSyncMsg +{ + constexpr static auto ProtocolName = "SwapSync"; + + constexpr static auto TypeRequest = "request"; + constexpr static auto TypeResponse = "response"; + + struct WaitForSwapSync + { + constexpr static auto name = "WaitForSwapSync"; + constexpr static auto argThreadTime = "ThreadTime"; + constexpr static auto argBarrierTime = "BarrierTime"; + }; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncService.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncService.cpp new file mode 100644 index 000000000000..1ea45657a4e2 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncService.cpp @@ -0,0 +1,107 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterSwapSyncService.h" +#include "DisplayClusterSwapSyncMsg.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Misc/DisplayClusterAppExit.h" +#include "Misc/DisplayClusterLog.h" + +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + + +FDisplayClusterSwapSyncService::FDisplayClusterSwapSyncService(const FString& addr, const int32 port) : + FDisplayClusterService(FString("SRV_SS"), addr, port), + BarrierSwap(GDisplayCluster->GetPrivateClusterMgr()->GetNodesAmount(), FString("SwapSync_barrier"), DisplayClusterConstants::net::BarrierWaitTimeout) +{ +} + +FDisplayClusterSwapSyncService::~FDisplayClusterSwapSyncService() +{ + Shutdown(); +} + + +bool FDisplayClusterSwapSyncService::Start() +{ + BarrierSwap.Activate(); + + return FDisplayClusterServer::Start(); +} + +void FDisplayClusterSwapSyncService::Shutdown() +{ + BarrierSwap.Deactivate(); + + return FDisplayClusterServer::Shutdown(); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterSessionListener +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterSwapSyncService::NotifySessionOpen(FDisplayClusterSession* pSession) +{ + FDisplayClusterService::NotifySessionOpen(pSession); +} + +void FDisplayClusterSwapSyncService::NotifySessionClose(FDisplayClusterSession* pSession) +{ + // Unblock waiting threads to allow current Tick() finish + BarrierSwap.Deactivate(); + + FDisplayClusterService::NotifySessionClose(pSession); +} + +FDisplayClusterMessage::Ptr FDisplayClusterSwapSyncService::ProcessMessage(FDisplayClusterMessage::Ptr msg) +{ + // Check the pointer + if (msg.IsValid() == false) + { + UE_LOG(LogDisplayClusterNetworkMsg, Error, TEXT("%s - Couldn't process the message"), *GetName()); + return nullptr; + } + + UE_LOG(LogDisplayClusterNetwork, Verbose, TEXT("%s - Processing message %s"), *GetName(), *msg->ToString()); + + // Check protocol and type + if (msg->GetProtocol() != FDisplayClusterSwapSyncMsg::ProtocolName || msg->GetType() != FDisplayClusterSwapSyncMsg::TypeRequest) + { + UE_LOG(LogDisplayClusterNetworkMsg, Error, TEXT("%s - Unsupported message type: %s"), *GetName(), *msg->ToString()); + return nullptr; + } + + // Initialize response message + FDisplayClusterMessage::Ptr response = FDisplayClusterMessage::Ptr(new FDisplayClusterMessage(msg->GetName(), FDisplayClusterSwapSyncMsg::TypeResponse, msg->GetProtocol())); + + // Dispatch the message + if (msg->GetName() == FDisplayClusterSwapSyncMsg::WaitForSwapSync::name) + { + double tTime = 0.f; + double bTime = 0.f; + + WaitForSwapSync(&tTime, &bTime); + + response->SetArg(FString(FDisplayClusterSwapSyncMsg::WaitForSwapSync::argThreadTime), tTime); + response->SetArg(FString(FDisplayClusterSwapSyncMsg::WaitForSwapSync::argBarrierTime), bTime); + + return response; + } + + // Being here means that we have no appropriate dispatch logic for this message + UE_LOG(LogDisplayClusterNetworkMsg, Warning, TEXT("%s - A dispatcher for this message hasn't been implemented yet <%s>"), *GetName(), *msg->ToString()); + return nullptr; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterSwapSyncProtocol +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterSwapSyncService::WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) +{ + if (BarrierSwap.Wait(pThreadWaitTime, pBarrierWaitTime) != FDisplayClusterBarrier::WaitResult::Ok) + { + FDisplayClusterAppExit::ExitApplication(FDisplayClusterAppExit::ExitType::NormalSoft, FString("Error on swap barrier. Exit required.")); + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncService.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncService.h new file mode 100644 index 000000000000..c6987586f338 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Network/Service/SwapSync/DisplayClusterSwapSyncService.h @@ -0,0 +1,46 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "Network/Service/DisplayClusterService.h" +#include "Network/Protocol/IPDisplayClusterSwapSyncProtocol.h" +#include "Network/DisplayClusterMessage.h" + +#include "Misc/DisplayClusterBarrier.h" + + +/** + * Swap synchronization server + */ +class FDisplayClusterSwapSyncService + : public FDisplayClusterService + , private IPDisplayClusterSwapSyncProtocol +{ +public: + FDisplayClusterSwapSyncService(const FString& addr, const int32 port); + virtual ~FDisplayClusterSwapSyncService(); + +public: + virtual bool Start() override; + virtual void Shutdown() override; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterSessionListener + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void NotifySessionOpen(FDisplayClusterSession* pSession) override; + virtual void NotifySessionClose(FDisplayClusterSession* pSession) override; + virtual FDisplayClusterMessage::Ptr ProcessMessage(FDisplayClusterMessage::Ptr msg) override; + +private: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterSwapSyncProtocol + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void WaitForSwapSync(double* pThreadWaitTime, double* pBarrierWaitTime) override; + + +private: + // Swap sync barrier + FDisplayClusterBarrier BarrierSwap; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Debug/DisplayClusterDeviceDebug.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Debug/DisplayClusterDeviceDebug.cpp new file mode 100644 index 000000000000..cb2145b32208 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Debug/DisplayClusterDeviceDebug.cpp @@ -0,0 +1,28 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceDebug.h" + + +FDisplayClusterDeviceDebug::FDisplayClusterDeviceDebug() +{ +} + +FDisplayClusterDeviceDebug::~FDisplayClusterDeviceDebug() +{ +} + + +void FDisplayClusterDeviceDebug::AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const +{ + const int rHeight = SizeY / 4; + + if (StereoPass == EStereoscopicPass::eSSP_LEFT_EYE) + { + SizeY -= rHeight; + } + else + { + Y = SizeY - rHeight; + SizeY = rHeight; + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Debug/DisplayClusterDeviceDebug.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Debug/DisplayClusterDeviceDebug.h new file mode 100644 index 000000000000..247e7ba08ed4 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Debug/DisplayClusterDeviceDebug.h @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Render/Devices/DisplayClusterDeviceBase.h" + + +/** + * Debug stereoscopic device (for development and test purposes) + */ +class FDisplayClusterDeviceDebug : public FDisplayClusterDeviceBase +{ +public: + FDisplayClusterDeviceDebug(); + virtual ~FDisplayClusterDeviceDebug(); + +protected: + virtual void AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const override; +}; + + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceBase.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceBase.cpp new file mode 100644 index 000000000000..e0459344a7df --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceBase.cpp @@ -0,0 +1,430 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceBase.h" + +#include "Cluster/IPDisplayClusterClusterManager.h" +#include "Cluster/Controller/IPDisplayClusterNodeController.h" +#include "Config/IPDisplayClusterConfigManager.h" +#include "Game/IPDisplayClusterGameManager.h" + +#include "DisplayClusterScreenComponent.h" + +#include "RHIStaticStates.h" +#include "Slate/SceneViewport.h" + +#include "Misc/DisplayClusterHelpers.h" +#include "Misc/DisplayClusterLog.h" + +#include "DisplayClusterGlobals.h" +#include "IPDisplayCluster.h" + +#include + + +FDisplayClusterDeviceBase::FDisplayClusterDeviceBase() : + FRHICustomPresent() +{ + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT(".ctor FDisplayClusterDeviceBase")); +} + +FDisplayClusterDeviceBase::~FDisplayClusterDeviceBase() +{ + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT(".dtor FDisplayClusterDeviceBase")); +} + +bool FDisplayClusterDeviceBase::Initialize() +{ + const FDisplayClusterConfigCustom cfgCustom = GDisplayCluster->GetPrivateConfigMgr()->GetConfigCustom(); + + if (cfgCustom.Args.Contains(FString(DisplayClusterStrings::cfg::data::custom::SwapInt))) + { + SwapInterval = FCString::Atoi(*cfgCustom.Args[FString(DisplayClusterStrings::cfg::data::custom::SwapInt)]); + } + + UE_LOG(LogDisplayClusterRender, Log, TEXT("Use swap interval: %d"), SwapInterval); + + return true; +} + +void FDisplayClusterDeviceBase::WaitForBufferSwapSync(int32& InOutSyncInterval) +{ + // Perform SW synchronization + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("Waiting for swap sync...")); + + // Policies below are available for any render device type + switch (SwapSyncPolicy) + { + case EDisplayClusterSwapSyncPolicy::None: + { + exec_BarrierWait(); + InOutSyncInterval = 0; + break; + } + + default: + { + UE_LOG(LogDisplayClusterRender, Warning, TEXT("Swap sync policy drop: %d"), (int)SwapSyncPolicy); + InOutSyncInterval = 0; + break; + } + } +} + +void FDisplayClusterDeviceBase::UpdateProjectionScreenDataForThisFrame() +{ + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("UpdateProjectionScreenDataForThisFrame")); + check(IsInGameThread()); + + // Store transformations of active projection screen + UDisplayClusterScreenComponent* pScreen = GDisplayCluster->GetPrivateGameMgr()->GetActiveScreen(); + if (pScreen) + { + ProjectionScreenLoc = pScreen->GetComponentLocation(); + ProjectionScreenRot = pScreen->GetComponentRotation(); + ProjectionScreenSize = pScreen->GetScreenSize(); + } +} + +void FDisplayClusterDeviceBase::exec_BarrierWait() +{ + double tTime = 0.f; + double bTime = 0.f; + + IPDisplayClusterNodeController* const pController = GDisplayCluster->GetPrivateClusterMgr()->GetController(); + if (pController) + { + pController->WaitForSwapSync(&tTime, &bTime); + } + + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("Render barrier wait: t=%lf b=%lf"), tTime, bTime); +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// IStereoRendering +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterDeviceBase::IsStereoEnabled() const +{ + //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("IsStereoEnabled")); + return true; +} + +bool FDisplayClusterDeviceBase::IsStereoEnabledOnNextFrame() const +{ + //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("IsStereoEnabledOnNextFrame")); + return true; +} + +bool FDisplayClusterDeviceBase::EnableStereo(bool stereo /*= true*/) +{ + //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("EnableStereo")); + return true; +} + +void FDisplayClusterDeviceBase::AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const +{ + X = ViewportArea.GetLocation().X; + SizeX = ViewportArea.GetSize().X; + + Y = ViewportArea.GetLocation().Y; + SizeY = ViewportArea.GetSize().Y; +} + +void FDisplayClusterDeviceBase::CalculateStereoViewOffset(const enum EStereoscopicPass StereoPassType, FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation) +{ + //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("CalculateStereoViewOffset")); + + check(IsInGameThread()); + check(WorldToMeters > 0.f); + + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("OLD ViewLoc: %s, ViewRot: %s"), *ViewLocation.ToString(), *ViewRotation.ToString()); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WorldToMeters: %f"), WorldToMeters); + + CurrentWorldToMeters = WorldToMeters; + + // View vector must be orthogonal to the projection plane. + ViewRotation = ProjectionScreenRot; + + const float ScaledEyeDist = EyeDist * CurrentWorldToMeters; + const float EyeOffset = ScaledEyeDist / 2.f; + const float PassOffset = (StereoPassType == EStereoscopicPass::eSSP_LEFT_EYE ? -EyeOffset : EyeOffset); + const float PassOffsetSwap = (bEyeSwap == true ? -PassOffset : PassOffset); + + // offset eye position along Y (right) axis of camera + UDisplayClusterCameraComponent* pCamera = GDisplayCluster->GetPrivateGameMgr()->GetActiveCamera(); + if(pCamera) + { + const FQuat eyeQuat = pCamera->GetComponentQuat(); + ViewLocation += eyeQuat.RotateVector(FVector(0.0f, PassOffsetSwap, 0.0f)); + } + + const int eyeIdx = (StereoPassType == EStereoscopicPass::eSSP_LEFT_EYE ? 0 : 1); + EyeLoc[eyeIdx] = ViewLocation; + EyeRot[eyeIdx] = ViewRotation; + + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("NEW ViewLoc: %s, ViewRot: %s"), *ViewLocation.ToString(), *ViewRotation.ToString()); +} + + +FMatrix FDisplayClusterDeviceBase::GetStereoProjectionMatrix(const enum EStereoscopicPass StereoPassType) const +{ + //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetStereoProjectionMatrix")); + + check(IsInGameThread()); + check(StereoPassType != EStereoscopicPass::eSSP_FULL); + + const float n = NearClipPlane; + const float f = FarClipPlane; + + // Half-size + const float hw = ProjectionScreenSize.X / 2.f * CurrentWorldToMeters; + const float hh = ProjectionScreenSize.Y / 2.f * CurrentWorldToMeters; + + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("StereoProjectionMatrix math: hw:%f hh:%f"), hw, hh); + + // Screen corners + const FVector pa = ProjectionScreenLoc + ProjectionScreenRot.Quaternion().RotateVector(GetProjectionScreenGeometryLBC(StereoPassType, hw, hh)); // left bottom corner + const FVector pb = ProjectionScreenLoc + ProjectionScreenRot.Quaternion().RotateVector(GetProjectionScreenGeometryRBC(StereoPassType, hw, hh)); // right bottom corner + const FVector pc = ProjectionScreenLoc + ProjectionScreenRot.Quaternion().RotateVector(GetProjectionScreenGeometryLTC(StereoPassType, hw, hh)); // left top corner + + // Screen vectors + FVector vr = pb - pa; // lb->rb normilized vector, right axis of projection screen + vr.Normalize(); + FVector vu = pc - pa; // lb->lt normilized vector, up axis of projection screen + vu.Normalize(); + FVector vn = -FVector::CrossProduct(vr, vu); // Projection plane normal. Use minus because of left-handed coordinate system + vn.Normalize(); + + const int eyeIdx = (StereoPassType == EStereoscopicPass::eSSP_LEFT_EYE ? 0 : 1); + const FVector pe = EyeLoc[eyeIdx]; + const FVector va = pa - pe; // camera -> lb + const FVector vb = pb - pe; // camera -> rb + const FVector vc = pc - pe; // camera -> lt + + const float d = -FVector::DotProduct(va, vn); // distance from eye to screen + const float ndifd = n / d; + const float l = FVector::DotProduct(vr, va) * ndifd; // distance to left screen edge + const float r = FVector::DotProduct(vr, vb) * ndifd; // distance to right screen edge + const float b = FVector::DotProduct(vu, va) * ndifd; // distance to bottom screen edge + const float t = FVector::DotProduct(vu, vc) * ndifd; // distance to top screen edge + + const float mx = 2.f * n / (r - l); + const float my = 2.f * n / (t - b); + const float ma = -(r + l) / (r - l); + const float mb = -(t + b) / (t - b); + const float mc = f / (f - n); + const float md = -(f * n) / (f - n); + const float me = 1.f; + + // Normal LHS + const FMatrix pm = FMatrix( + FPlane(mx, 0, 0, 0), + FPlane(0, my, 0, 0), + FPlane(ma, mb, mc, me), + FPlane(0, 0, md, 0)); + + // Invert Z-axis (UE4 uses Z-inverted LHS) + const FMatrix flipZ = FMatrix( + FPlane(1, 0, 0, 0), + FPlane(0, 1, 0, 0), + FPlane(0, 0, -1, 0), + FPlane(0, 0, 1, 1)); + + const FMatrix result(pm * flipZ); + + return result; +} + +void FDisplayClusterDeviceBase::InitCanvasFromView(class FSceneView* InView, class UCanvas* Canvas) +{ + //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("InitCanvasFromView")); +} + +void FDisplayClusterDeviceBase::UpdateViewport(bool bUseSeparateRenderTarget, const class FViewport& Viewport, class SViewport* ViewportWidget) +{ + //UE_LOG(LogDisplayClusterRender, Verbose, TEXT("UpdateViewport")); + check(IsInGameThread()); + + // Update projection screen data + UpdateProjectionScreenDataForThisFrame(); + + // Save current dimensions + ViewportSize = Viewport.GetSizeXY(); + BackBuffSize = Viewport.GetRenderTargetTextureSizeXY(); + + // If no custom area specified the full viewport area will be used + if (ViewportArea.IsValid() == false) + { + ViewportArea.SetLocation(FIntPoint::ZeroValue); + ViewportArea.SetSize(Viewport.GetSizeXY()); + } + + // Store viewport + CurrentViewport = (FViewport*)&Viewport; + Viewport.GetViewportRHI()->SetCustomPresent(this); +} + +void FDisplayClusterDeviceBase::CalculateRenderTargetSize(const class FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY) +{ + //UE_LOG(LogDisplayClusterRender, Log, TEXT("FDisplayClusterDeviceBase::CalculateRenderTargetSize")); + check(IsInGameThread()); + + InOutSizeX = Viewport.GetSizeXY().X; + // Add one pixel height line for right eye (will be skipped on copy) + InOutSizeY = Viewport.GetSizeXY().Y; + + check(InOutSizeX > 0 && InOutSizeY > 0); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FRHICustomPresent +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterDeviceBase::OnBackBufferResize() +{ + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("OnBackBufferResize")); + + //@todo: see comment below + // if we are in the middle of rendering: prevent from calling EndFrame + //if (RenderContext.IsValid()) + //{ + // RenderContext->bFrameBegun = false; + //} +} + +bool FDisplayClusterDeviceBase::Present(int32& InOutSyncInterval) +{ + UE_LOG(LogDisplayClusterRender, Warning, TEXT("Present - default handler implementation. Check stereo device instantiation.")); + + // Default behavior + // Return false to force clean screen. This will indicate that something is going wrong + // or particular stereo device hasn't been implemented appropriately yet. + return false; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterStereoDevice +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterDeviceBase::SetViewportArea(const FIntPoint& loc, const FIntPoint& size) +{ + UE_LOG(LogDisplayClusterRender, Log, TEXT("SetViewportArea: loc=%s size=%s"), *loc.ToString(), *size.ToString()); + + FScopeLock lock(&InternalsSyncScope); + ViewportArea.SetLocation(loc); + ViewportArea.SetSize(size); +} + +void FDisplayClusterDeviceBase::SetDesktopStereoParams(float FOV) +{ + UE_LOG(LogDisplayClusterRender, Log, TEXT("SetDesktopStereoParams: FOV=%f"), FOV); + //@todo +} + +void FDisplayClusterDeviceBase::SetDesktopStereoParams(const FVector2D& screenSize, const FIntPoint& screenRes, float screenDist) +{ + UE_LOG(LogDisplayClusterRender, Log, TEXT("SetDesktopStereoParams")); + + FVector2D size = screenSize; + float dist = screenDist; + + //@todo: +} + +void FDisplayClusterDeviceBase::SetInterpupillaryDistance(float dist) +{ + UE_LOG(LogDisplayClusterRender, Log, TEXT("SetInterpupillaryDistance: %f"), dist); + FScopeLock lock(&InternalsSyncScope); + EyeDist = dist; +} + +float FDisplayClusterDeviceBase::GetInterpupillaryDistance() const +{ + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetInterpupillaryDistance: %f"), EyeDist); + FScopeLock lock(&InternalsSyncScope); + return EyeDist; +} + +void FDisplayClusterDeviceBase::SetEyesSwap(bool swap) +{ + UE_LOG(LogDisplayClusterRender, Log, TEXT("SetEyesSwap: %s"), DisplayClusterHelpers::str::BoolToStr(swap)); + FScopeLock lock(&InternalsSyncScope); + bEyeSwap = swap; +} + +bool FDisplayClusterDeviceBase::GetEyesSwap() const +{ + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetEyesSwap: %s"), DisplayClusterHelpers::str::BoolToStr(bEyeSwap)); + FScopeLock lock(&InternalsSyncScope); + return bEyeSwap; +} + +bool FDisplayClusterDeviceBase::ToggleEyesSwap() +{ + { + FScopeLock lock(&InternalsSyncScope); + bEyeSwap = !bEyeSwap; + } + + UE_LOG(LogDisplayClusterRender, Log, TEXT("ToggleEyesSwap: swap=%s"), DisplayClusterHelpers::str::BoolToStr(bEyeSwap)); + return bEyeSwap; +} + +void FDisplayClusterDeviceBase::SetOutputFlip(bool flipH, bool flipV) +{ + UE_LOG(LogDisplayClusterRender, Log, TEXT("SetOutputFlip: horizontal=%s vertical=%s"), DisplayClusterHelpers::str::BoolToStr(flipH), DisplayClusterHelpers::str::BoolToStr(flipV)); + + FScopeLock lock(&InternalsSyncScope); + bFlipHorizontal = flipH; + bFlipVertical = flipV; +} + +void FDisplayClusterDeviceBase::GetOutputFlip(bool& flipH, bool& flipV) const +{ + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetOutputFlip: horizontal=%s vertical=%s"), DisplayClusterHelpers::str::BoolToStr(bFlipHorizontal), DisplayClusterHelpers::str::BoolToStr(bFlipVertical)); + + FScopeLock lock(&InternalsSyncScope); + flipH = bFlipHorizontal; + flipV = bFlipVertical; +} + +void FDisplayClusterDeviceBase::SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy) +{ + UE_LOG(LogDisplayClusterRender, Log, TEXT("Swap sync policy: %d"), (int)policy); + + // Since not all our devices are opengl compatible in terms of implementation + // we have to perform some wrapping logic for the policies. + switch (policy) + { + // Policies below are available for any render device type + case EDisplayClusterSwapSyncPolicy::None: + SwapSyncPolicy = policy; + break; + + default: + UE_LOG(LogDisplayClusterRender, Error, TEXT("Unsupported policy type: %d"), (int)policy); + SwapSyncPolicy = EDisplayClusterSwapSyncPolicy::None; + break; + } +} + +EDisplayClusterSwapSyncPolicy FDisplayClusterDeviceBase::GetSwapSyncPolicy() const +{ + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("GetSwapSyncPolicy: policy=%d"), (int)SwapSyncPolicy); + return SwapSyncPolicy; +} + +void FDisplayClusterDeviceBase::GetCullingDistance(float& NearDistance, float& FarDistance) const +{ + FScopeLock lock(&InternalsSyncScope); + NearDistance = NearClipPlane; + FarDistance = FarClipPlane; +} + +void FDisplayClusterDeviceBase::SetCullingDistance(float NearDistance, float FarDistance) +{ + UE_LOG(LogDisplayClusterRender, Log, TEXT("New culling distance: NCP=%f, FCP=%f"), NearDistance, FarDistance); + + FScopeLock lock(&InternalsSyncScope); + NearClipPlane = NearDistance; + FarClipPlane = FarDistance; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceBase.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceBase.h new file mode 100644 index 000000000000..dbcfdeeb31df --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceBase.h @@ -0,0 +1,208 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +#include "RHI.h" +#include "RHIResources.h" +#include "StereoRendering.h" +#include "StereoRenderTargetManager.h" + +#include "Render/IDisplayClusterStereoDevice.h" +#include "Render/Devices/DisplayClusterViewportArea.h" + + +/** + * Abstract render device + */ +class FDisplayClusterDeviceBase + : public IStereoRendering + , public IStereoRenderTargetManager + , public IDisplayClusterStereoDevice + , public FRHICustomPresent +{ +public: + FDisplayClusterDeviceBase(); + virtual ~FDisplayClusterDeviceBase(); + +public: + virtual bool Initialize(); + +protected: + + inline uint32 GetSwapInt() const + { return SwapInterval; } + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IStereoRendering + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool IsStereoEnabled() const override; + virtual bool IsStereoEnabledOnNextFrame() const override; + virtual bool EnableStereo(bool stereo = true) override; + virtual void AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const override; + virtual void CalculateStereoViewOffset(const enum EStereoscopicPass StereoPassType, FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation) override; + virtual FMatrix GetStereoProjectionMatrix(const enum EStereoscopicPass StereoPassType) const override; + virtual void InitCanvasFromView(class FSceneView* InView, class UCanvas* Canvas) override; + virtual IStereoRenderTargetManager* GetRenderTargetManager() override + { return this; } + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IStereoRenderTargetManager + ////////////////////////////////////////////////////////////////////////////////////////////// + /** + * Whether a separate render target should be used or not. + * In case the stereo rendering implementation does not require special handling of separate render targets + * at all, it can leave out implementing this interface completely and simply let the default implementation + * of IStereoRendering::GetRenderTargetManager() return nullptr. + */ + virtual bool ShouldUseSeparateRenderTarget() const override + { return false; } + + /** + * Updates viewport for direct rendering of distortion. Should be called on a game thread. + * + * @param bUseSeparateRenderTarget Set to true if a separate render target will be used. Can potentiallt be true even if ShouldUseSeparateRenderTarget() returned false earlier. + * @param Viewport The Viewport instance calling this method. + * @param ViewportWidget (optional) The Viewport widget containing the view. Can be used to access SWindow object. + */ + virtual void UpdateViewport(bool bUseSeparateRenderTarget, const class FViewport& Viewport, class SViewport* ViewportWidget = nullptr) override; + + /** + * Calculates dimensions of the render target texture for direct rendering of distortion. + */ + virtual void CalculateRenderTargetSize(const class FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY) override; + + /** + * Returns true, if render target texture must be re-calculated. + */ + virtual bool NeedReAllocateViewportRenderTarget(const class FViewport& Viewport) override + { return false; } + + /** + * Returns true, if render target texture must be re-calculated. + */ + virtual bool NeedReAllocateDepthTexture(const TRefCountPtr& DepthTarget) override + { return false; } + + /** + * Returns number of required buffered frames. + */ + virtual uint32 GetNumberOfBufferedFrames() const override + { return 1; } + + /** + * Allocates a render target texture. + * The default implementation always return false to indicate that the default texture allocation should be used instead. + * + * @param Index (in) index of the buffer, changing from 0 to GetNumberOfBufferedFrames() + * @return true, if texture was allocated; false, if the default texture allocation should be used. + */ + virtual bool AllocateRenderTargetTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, uint32 Flags, uint32 TargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples = 1) override + { return false; } + + /** + * Allocates a depth texture. + * + * @param Index (in) index of the buffer, changing from 0 to GetNumberOfBufferedFrames() + * @return true, if texture was allocated; false, if the default texture allocation should be used. + */ + virtual bool AllocateDepthTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, uint32 Flags, uint32 TargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples = 1) { return false; } + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // FRHICustomPresent + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void OnBackBufferResize() override; + // Called from render thread to see if a native present will be requested for this frame. + // @return true if native Present will be requested for this frame; false otherwise. Must + // match value subsequently returned by Present for this frame. + virtual bool NeedsNativePresent() override + { return true; } + + virtual bool Present(int32& InOutSyncInterval) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterStereoDevice + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void SetViewportArea(const FIntPoint& loc, const FIntPoint& size) override; + virtual void SetDesktopStereoParams(float FOV) override; + virtual void SetDesktopStereoParams(const FVector2D& screenSize, const FIntPoint& screenRes, float screenDist) override; + virtual void SetInterpupillaryDistance(float dist) override; + virtual float GetInterpupillaryDistance() const override; + virtual void SetEyesSwap(bool swap) override; + virtual bool GetEyesSwap() const override; + virtual bool ToggleEyesSwap() override; + virtual void SetOutputFlip(bool flipH, bool flipV) override; + virtual void GetOutputFlip(bool& flipH, bool& flipV) const override; + virtual void SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy) override; + virtual EDisplayClusterSwapSyncPolicy GetSwapSyncPolicy() const override; + virtual void GetCullingDistance(float& NearDistance, float& FarDistance) const override; + virtual void SetCullingDistance(float NearDistance, float FarDistance) override; + +protected: + // Implements buffer swap synchronization mechanism + virtual void WaitForBufferSwapSync(int32& InOutSyncInterval); + // Retrieves the projections screen data for current frame + void UpdateProjectionScreenDataForThisFrame(); + + // Custom projection screen geometry (hw - half-width, hh - half-height of projection screen) + // Left bottom corner (from camera point view) + virtual FVector GetProjectionScreenGeometryLBC(const enum EStereoscopicPass StereoPassType, const float& hw, const float& hh) const + { return FVector(0.f, -hw, -hh);} + + // Right bottom corner (from camera point view) + virtual FVector GetProjectionScreenGeometryRBC(const enum EStereoscopicPass StereoPassType, const float& hw, const float& hh) const + { return FVector(0.f, hw, -hh);} + + // Left top corner (from camera point view) + virtual FVector GetProjectionScreenGeometryLTC(const enum EStereoscopicPass StereoPassType, const float& hw, const float& hh) const + { return FVector(0.f, -hw, hh);} + +protected: + void exec_BarrierWait(); + +protected: + // Data access synchronization + mutable FCriticalSection InternalsSyncScope; + + // Viewport and back buffer size + FIntPoint BackBuffSize = { 0, 0 }; + FIntPoint ViewportSize = { 0, 0 }; + + // Stereo parameters + float EyeDist = 0.064f; // meters + bool bEyeSwap = false; + FVector EyeLoc[2] = { FVector::ZeroVector, FVector::ZeroVector }; + FRotator EyeRot[2] = { FRotator::ZeroRotator, FRotator::ZeroRotator }; + + // Current world scale + float CurrentWorldToMeters = 100.f; + + // Viewport area settings + FDisplayClusterViewportArea ViewportArea; + + // Render config + bool bFlipHorizontal = false; + bool bFlipVertical = false; + + // Clipping plane + float NearClipPlane = GNearClippingPlane; + float FarClipPlane = 2000000.f; + + // Projection screen data + FVector ProjectionScreenLoc; + FRotator ProjectionScreenRot; + FVector2D ProjectionScreenSize; + + uint32 SwapInterval = 1; + + // Swap sync policy + EDisplayClusterSwapSyncPolicy SwapSyncPolicy = EDisplayClusterSwapSyncPolicy::None; + +protected: + FViewport* CurrentViewport; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceInternals.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceInternals.cpp new file mode 100644 index 000000000000..39b8979b097a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceInternals.cpp @@ -0,0 +1,95 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceInternals.h" + + +#if PLATFORM_WINDOWS +PFNWGLSWAPINTERVALEXTPROC DisplayCluster_wglSwapIntervalEXT_ProcAddress = nullptr; + +PFNWGLJOINSWAPGROUPNVPROC DisplayCluster_wglJoinSwapGroupNV_ProcAddress = nullptr; +PFNWGLBINDSWAPBARRIERNVPROC DisplayCluster_wglBindSwapBarrierNV_ProcAddress = nullptr; +PFNWGLQUERYSWAPGROUPNVPROC DisplayCluster_wglQuerySwapGroupNV_ProcAddress = nullptr; +PFNWGLQUERYMAXSWAPGROUPSNVPROC DisplayCluster_wglQueryMaxSwapGroupsNV_ProcAddress = nullptr; +PFNWGLQUERYFRAMECOUNTNVPROC DisplayCluster_wglQueryFrameCountNV_ProcAddress = nullptr; +PFNWGLRESETFRAMECOUNTNVPROC DisplayCluster_wglResetFrameCountNV_ProcAddress = nullptr; + + +// Copy/pasted from OpenGLDrv.cpp +static void DisplayClusterGetExtensionsString(FString& ExtensionsString) +{ + GLint ExtensionCount = 0; + ExtensionsString = TEXT(""); + if (FOpenGL::SupportsIndexedExtensions()) + { + glGetIntegerv(GL_NUM_EXTENSIONS, &ExtensionCount); + for (int32 ExtensionIndex = 0; ExtensionIndex < ExtensionCount; ++ExtensionIndex) + { + const ANSICHAR* ExtensionString = FOpenGL::GetStringIndexed(GL_EXTENSIONS, ExtensionIndex); + + ExtensionsString += TEXT(" "); + ExtensionsString += ANSI_TO_TCHAR(ExtensionString); + } + } + else + { + const ANSICHAR* GlGetStringOutput = (const ANSICHAR*)glGetString(GL_EXTENSIONS); + if (GlGetStringOutput) + { + ExtensionsString += GlGetStringOutput; + ExtensionsString += TEXT(" "); + } + } +} + +// https://www.opengl.org/wiki/Load_OpenGL_Functions +static void* DisplayClusterGetGLFuncAddress(const char *name) +{ + HMODULE module = LoadLibraryA("opengl32.dll"); + if (module) + { + return (void *)GetProcAddress(module, name); + } + else + { + return nullptr; + } +} + +// Copy/pasted from OpenGLDevice.cpp +// static void InitRHICapabilitiesForGL() +void DisplayClusterInitCapabilitiesForGL() +{ + bool bWindowsSwapControlExtensionPresent = false; + { + FString ExtensionsString; + DisplayClusterGetExtensionsString(ExtensionsString); + + if (ExtensionsString.Contains(TEXT("WGL_EXT_swap_control"))) + { + bWindowsSwapControlExtensionPresent = true; + } + } + +#pragma warning(push) +#pragma warning(disable:4191) + if (bWindowsSwapControlExtensionPresent) + { + DisplayCluster_wglSwapIntervalEXT_ProcAddress = (PFNWGLSWAPINTERVALEXTPROC)wglGetProcAddress("wglSwapIntervalEXT"); + } + + DisplayCluster_wglJoinSwapGroupNV_ProcAddress = (PFNWGLJOINSWAPGROUPNVPROC)wglGetProcAddress("wglJoinSwapGroupNV"); + DisplayCluster_wglBindSwapBarrierNV_ProcAddress = (PFNWGLBINDSWAPBARRIERNVPROC)wglGetProcAddress("wglBindSwapBarrierNV"); + DisplayCluster_wglQuerySwapGroupNV_ProcAddress = (PFNWGLQUERYSWAPGROUPNVPROC)wglGetProcAddress("wglQuerySwapGroupNV"); + DisplayCluster_wglQueryMaxSwapGroupsNV_ProcAddress = (PFNWGLQUERYMAXSWAPGROUPSNVPROC)wglGetProcAddress("wglQueryMaxSwapGroupsNV"); + DisplayCluster_wglQueryFrameCountNV_ProcAddress = (PFNWGLQUERYFRAMECOUNTNVPROC)wglGetProcAddress("wglQueryFrameCountNV"); + DisplayCluster_wglResetFrameCountNV_ProcAddress = (PFNWGLRESETFRAMECOUNTNVPROC)wglGetProcAddress("wglResetFrameCountNV"); + +#pragma warning(pop) +} +#endif + + + +#if PLATFORM_LINUX +//@todo: Implementation for Linux +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceInternals.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceInternals.h new file mode 100644 index 000000000000..59faf5ece44d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterDeviceInternals.h @@ -0,0 +1,103 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + + +#if PLATFORM_WINDOWS + +#include "D3D11RHIPrivate.h" +#include "D3D11Util.h" + +//------------------------------------------------------------------------------------------------- +// D3D12 +//------------------------------------------------------------------------------------------------- + +#define GetD3D11CubeFace GetD3D12CubeFace +#define VerifyD3D11Result VerifyD3D12Result +#define GetD3D11TextureFromRHITexture GetD3D12TextureFromRHITexture +#define FRingAllocation FRingAllocation_D3D12 +#define GetRenderTargetFormat GetRenderTargetFormat_D3D12 +#define ED3D11ShaderOffsetBuffer ED3D12ShaderOffsetBuffer +#define FindShaderResourceDXGIFormat FindShaderResourceDXGIFormat_D3D12 +#define FindUnorderedAccessDXGIFormat FindUnorderedAccessDXGIFormat_D3D12 +#define FindDepthStencilDXGIFormat FindDepthStencilDXGIFormat_D3D12 +#define HasStencilBits HasStencilBits_D3D12 +#define FVector4VertexDeclaration FVector4VertexDeclaration_D3D12 +#define GLOBAL_CONSTANT_BUFFER_INDEX GLOBAL_CONSTANT_BUFFER_INDEX_D3D12 +#define MAX_CONSTANT_BUFFER_SLOTS MAX_CONSTANT_BUFFER_SLOTS_D3D12 +#define FD3DGPUProfiler FD3D12GPUProfiler +#define FRangeAllocator FRangeAllocator_D3D12 + +#include "D3D12RHIPrivate.h" +#include "D3D12Util.h" + +#undef GetD3D11CubeFace +#undef VerifyD3D11Result +#undef GetD3D11TextureFromRHITexture +#undef FRingAllocation +#undef GetRenderTargetFormat +#undef ED3D11ShaderOffsetBuffer +#undef FindShaderResourceDXGIFormat +#undef FindUnorderedAccessDXGIFormat +#undef FindDepthStencilDXGIFormat +#undef HasStencilBits +#undef FVector4VertexDeclaration +#undef GLOBAL_CONSTANT_BUFFER_INDEX +#undef MAX_CONSTANT_BUFFER_SLOTS +#undef FD3DGPUProfiler +#undef FRangeAllocator + + +#include "../../OpenGLDrv/Public/OpenGLDrv.h" +#include "../../OpenGLDrv/Public/OpenGLResources.h" +#include "OpenGLResources.h" + +extern PFNWGLSWAPINTERVALEXTPROC DisplayCluster_wglSwapIntervalEXT_ProcAddress; + +extern PFNWGLJOINSWAPGROUPNVPROC DisplayCluster_wglJoinSwapGroupNV_ProcAddress; +extern PFNWGLBINDSWAPBARRIERNVPROC DisplayCluster_wglBindSwapBarrierNV_ProcAddress; +extern PFNWGLQUERYSWAPGROUPNVPROC DisplayCluster_wglQuerySwapGroupNV_ProcAddress; +extern PFNWGLQUERYMAXSWAPGROUPSNVPROC DisplayCluster_wglQueryMaxSwapGroupsNV_ProcAddress; +extern PFNWGLQUERYFRAMECOUNTNVPROC DisplayCluster_wglQueryFrameCountNV_ProcAddress; +extern PFNWGLRESETFRAMECOUNTNVPROC DisplayCluster_wglResetFrameCountNV_ProcAddress; + + +void DisplayClusterInitCapabilitiesForGL(); + +// This is redeclaration of WINDOWS specific FPlatformOpenGLContext +// which is declared in private OpenGLWindows.cpp file. +//@note: Keep it synced with original type (Engine\Source\Runtime\OpenGLDrv\Private\Windows\OpenGLWindows.cpp) +struct FPlatformOpenGLContext +{ + HWND WindowHandle; + HDC DeviceContext; + HGLRC OpenGLContext; + bool bReleaseWindowOnDestroy; + int32 SyncInterval; + GLuint ViewportFramebuffer; + GLuint VertexArrayObject; // one has to be generated and set for each context (OpenGL 3.2 Core requirements) + GLuint BackBufferResource; + GLenum BackBufferTarget; +}; +#endif + + + +#if PLATFORM_LINUX +// This is redeclaration of LINUX specific FPlatformOpenGLContext +// which is declared in private OpenGLWindows.cpp file. +//@note: Keep it synced with original type (Engine\Source\Runtime\OpenGLDrv\Private\Linux\OpenGLLinux.cpp) +struct FPlatformOpenGLContext +{ + SDL_HWindow hWnd; + SDL_HGLContext hGLContext; // this is a (void*) pointer + + bool bReleaseWindowOnDestroy; + int32 SyncInterval; + GLuint ViewportFramebuffer; + GLuint VertexArrayObject; // one has to be generated and set for each context (OpenGL 3.2 Core requirements) +}; + +//@note: Place here any Linux targeted device implementations +#endif + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterViewportArea.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterViewportArea.h new file mode 100644 index 000000000000..97c33fbe4fa3 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/DisplayClusterViewportArea.h @@ -0,0 +1,52 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + + +/** + * Viewport area + */ +class FDisplayClusterViewportArea +{ +public: + FDisplayClusterViewportArea() : + Location(FIntPoint::ZeroValue), + Size(FIntPoint::ZeroValue) + { } + + FDisplayClusterViewportArea(const FIntPoint& loc, const FIntPoint& size) : + Location(loc), + Size(size) + { } + + FDisplayClusterViewportArea(int32 x, int32 y, int32 w, int32 h) : + Location(FIntPoint(x, y)), + Size(FIntPoint(w, h)) + { } + +public: + bool IsValid() const + { return Size.X > 0 && Size.Y > 0; } + + FIntPoint GetLocation() const + { return Location; } + + FIntPoint GetSize() const + { return Size; } + + void SetLocation(const FIntPoint& loc) + { Location = loc; } + + void SetLocation(int32 x, int32 y) + { Location = FIntPoint(x, y); } + + void SetSize(const FIntPoint& size) + { Size = size; } + + void SetSize(int32 w, int32 h) + { Size = FIntPoint(w, h); } + +private: + FIntPoint Location; + FIntPoint Size; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.cpp new file mode 100644 index 000000000000..fcf8aba362fd --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.cpp @@ -0,0 +1,38 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceMonoscopicD3D11.h" + +#include "D3D11Viewport.h" +#include "D3D11Resources.h" + + +FDisplayClusterDeviceMonoscopicD3D11::FDisplayClusterDeviceMonoscopicD3D11(): + FDisplayClusterDeviceQuadBufferStereoD3D11() +{ + +} + +FDisplayClusterDeviceMonoscopicD3D11::~FDisplayClusterDeviceMonoscopicD3D11() +{ + +} + +bool FDisplayClusterDeviceMonoscopicD3D11::Present(int32& InOutSyncInterval) +{ + FD3D11Viewport* viewport = static_cast(CurrentViewport->GetViewportRHI().GetReference()); + +#if !WITH_EDITOR + // Issue frame event + viewport->IssueFrameEvent(); + // Wait until GPU finish last frame commands + viewport->WaitForFrameEventCompletion(); +#endif + + // Sync all nodes + exec_BarrierWait(); + + IDXGISwapChain* swapchain = (IDXGISwapChain*)viewport->GetSwapChain(); + swapchain->Present(GetSwapInt(), 0); + + return false; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.h new file mode 100644 index 000000000000..f2c208e83b61 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.h @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + + +/** + * Monoscopic emulation device (DirectX 11) + */ +class FDisplayClusterDeviceMonoscopicD3D11 : public FDisplayClusterDeviceQuadBufferStereoD3D11 +{ +public: + FDisplayClusterDeviceMonoscopicD3D11(); + virtual ~FDisplayClusterDeviceMonoscopicD3D11(); + + virtual bool ShouldUseSeparateRenderTarget() const override { return false; }; +protected: + virtual bool Present(int32& InOutSyncInterval) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.cpp new file mode 100644 index 000000000000..6eb36c745de9 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.cpp @@ -0,0 +1,38 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceMonoscopicD3D12.h" + +#include "D3D12Viewport.h" +#include "D3D12Resources.h" + + +FDisplayClusterDeviceMonoscopicD3D12::FDisplayClusterDeviceMonoscopicD3D12(): + FDisplayClusterDeviceQuadBufferStereoD3D12() +{ + +} + +FDisplayClusterDeviceMonoscopicD3D12::~FDisplayClusterDeviceMonoscopicD3D12() +{ + +} + +bool FDisplayClusterDeviceMonoscopicD3D12::Present(int32& InOutSyncInterval) +{ + FD3D12Viewport* viewport = static_cast(CurrentViewport->GetViewportRHI().GetReference()); + +#if !WITH_EDITOR + // Issue frame event + viewport->IssueFrameEvent(); + // Wait until GPU finish last frame commands + viewport->WaitForFrameEventCompletion(); +#endif + + // Sync all nodes + exec_BarrierWait(); + + IDXGISwapChain1* swapchain1 = (IDXGISwapChain1*)viewport->GetSwapChain(); + swapchain1->Present(GetSwapInt(), 0); + + return false; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.h new file mode 100644 index 000000000000..97be316bd700 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.h @@ -0,0 +1,24 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + + +/** + * Monoscopic emulation device (DirectX 12) + */ +class FDisplayClusterDeviceMonoscopicD3D12 : public FDisplayClusterDeviceQuadBufferStereoD3D12 +{ +public: + FDisplayClusterDeviceMonoscopicD3D12(); + virtual ~FDisplayClusterDeviceMonoscopicD3D12(); + + virtual bool ShouldUseSeparateRenderTarget() const override + { return false; } + +protected: + virtual bool Present(int32& InOutSyncInterval) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.cpp new file mode 100644 index 000000000000..fcf8ae4afb0e --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.cpp @@ -0,0 +1,62 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceMonoscopicOpenGL.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + +#include "Misc/DisplayClusterLog.h" + +#include + + +FDisplayClusterDeviceMonoscopicOpenGL::FDisplayClusterDeviceMonoscopicOpenGL() +{ + +} + +FDisplayClusterDeviceMonoscopicOpenGL::~FDisplayClusterDeviceMonoscopicOpenGL() +{ + +} + +bool FDisplayClusterDeviceMonoscopicOpenGL::Present(int32& InOutSyncInterval) +{ + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("FDisplayClusterDeviceQuadBufferStereoOpenGL::Present")); + + const int halfSizeY = BackBuffSize.Y / 2; + int dstX1 = 0; + int dstX2 = BackBuffSize.X; + + // Convert to left bottom origin and flip Y + int dstY1 = ViewportSize.Y; + int dstY2 = 0; + + if (bFlipHorizontal) + std::swap(dstX1, dstX2); + + if (bFlipVertical) + std::swap(dstY1, dstY2); + + + FOpenGLViewport* pOglViewport = static_cast(CurrentViewport->GetViewportRHI().GetReference()); + check(pOglViewport); + FPlatformOpenGLContext* const pContext = pOglViewport->GetGLContext(); + check(pContext && pContext->DeviceContext); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, pContext->ViewportFramebuffer); + glReadBuffer(GL_COLOR_ATTACHMENT0); + + glDrawBuffer(GL_BACK); + glBlitFramebuffer( + 0, 0, BackBuffSize.X, halfSizeY, + dstX1, dstY1, dstX2, dstY2, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + + // Perform buffers swap logic + SwapBuffers(pOglViewport, InOutSyncInterval); + REPORT_GL_END_BUFFER_EVENT_FOR_FRAME_DUMP(); + + return false; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.h new file mode 100644 index 000000000000..01a142093fb0 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.h @@ -0,0 +1,20 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + + +/** + * Monoscopic emulation device (OpenGL 3 and 4) + */ +class FDisplayClusterDeviceMonoscopicOpenGL : public FDisplayClusterDeviceQuadBufferStereoOpenGL +{ +public: + FDisplayClusterDeviceMonoscopicOpenGL(); + virtual ~FDisplayClusterDeviceMonoscopicOpenGL(); + +protected: + virtual bool Present(int32& InOutSyncInterval) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoBase.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoBase.cpp new file mode 100644 index 000000000000..963e948ad102 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoBase.cpp @@ -0,0 +1,93 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceQuadBufferStereoBase.h" + +#include "Render/Devices/DisplayClusterDeviceInternals.h" + +#include + + +FDisplayClusterDeviceQuadBufferStereoBase::FDisplayClusterDeviceQuadBufferStereoBase() : + FDisplayClusterDeviceBase() +{ +} + +FDisplayClusterDeviceQuadBufferStereoBase::~FDisplayClusterDeviceQuadBufferStereoBase() +{ +} + +bool FDisplayClusterDeviceQuadBufferStereoBase::NeedReAllocateViewportRenderTarget(const class FViewport& Viewport) +{ + //UE_LOG(LogDisplayClusterRender, Log, TEXT("FDisplayClusterDeviceMonoscopic::NeedReAllocateViewportRenderTarget")); + check(IsInGameThread()); + + const FIntPoint rtSize = Viewport.GetRenderTargetTextureSizeXY(); + uint32 newSizeX = rtSize.X; + uint32 newSizeY = rtSize.Y; + + // Perform size calculation + CalculateRenderTargetSize(Viewport, newSizeX, newSizeY); + + // Render target need to be re-allocated if its current size is invalid + if (newSizeX != rtSize.X || newSizeY != rtSize.Y) + { + return true; + } + + // No need to re-allocate + return false; +} + +void FDisplayClusterDeviceQuadBufferStereoBase::CalculateRenderTargetSize(const class FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY) +{ + check(IsInGameThread()); + + InOutSizeX = Viewport.GetSizeXY().X; + InOutSizeY = Viewport.GetSizeXY().Y * 2; + + check(InOutSizeX > 0 && InOutSizeY > 0); +} + + +bool FDisplayClusterDeviceQuadBufferStereoBase::ShouldUseSeparateRenderTarget() const +{ + return true; +} + +void FDisplayClusterDeviceQuadBufferStereoBase::AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const +{ + uint32 screentHeight = SizeY; + FDisplayClusterDeviceBase::AdjustViewRect(StereoPass, X, Y, SizeX, SizeY); + + if (StereoPass == EStereoscopicPass::eSSP_RIGHT_EYE) + { + Y += screentHeight; + } +} + +/* +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterStereoDevice +////////////////////////////////////////////////////////////////////////////////////////////// +void FDisplayClusterDeviceQuadBufferStereoBase::SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy) +{ + FScopeLock lock(&InternalsSyncScope); + UE_LOG(LogDisplayClusterRender, Log, TEXT("Swap sync policy: %d"), (int)policy); + + switch (policy) + { + // Policies below are supported by all child implementations + case EDisplayClusterSwapSyncPolicy::SoftSwapSync: + case EDisplayClusterSwapSyncPolicy::NvSwapSync: + { + SwapSyncPolicy = policy; + break; + } + + default: + // Forward the policy type to the upper level + FDisplayClusterDeviceBase::SetSwapSyncPolicy(policy); + break; + } +} +*/ diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoBase.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoBase.h new file mode 100644 index 000000000000..3f525b018a08 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoBase.h @@ -0,0 +1,29 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Render/Devices/DisplayClusterDeviceBase.h" + + +/** + * Abstract frame sequenced active stereo device + */ +class FDisplayClusterDeviceQuadBufferStereoBase : public FDisplayClusterDeviceBase +{ +public: + FDisplayClusterDeviceQuadBufferStereoBase(); + virtual ~FDisplayClusterDeviceQuadBufferStereoBase(); + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IStereoRendering + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool NeedReAllocateViewportRenderTarget(const class FViewport& Viewport) override; + virtual void AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const override; + virtual bool ShouldUseSeparateRenderTarget() const override; + virtual void CalculateRenderTargetSize(const class FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY) override; + +protected: + mutable FCriticalSection InternalsSyncScope; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.cpp new file mode 100644 index 000000000000..860ee184f262 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.cpp @@ -0,0 +1,105 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceQuadBufferStereoD3D11.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + +#include "Misc/DisplayClusterLog.h" +#include "D3D11Viewport.h" +#include "RHI.h" +#include "RHICommandList.h" + + +FDisplayClusterDeviceQuadBufferStereoD3D11::FDisplayClusterDeviceQuadBufferStereoD3D11() : + FDisplayClusterDeviceQuadBufferStereoBase() +{ + dxgi_present_parameters = { 0, nullptr, nullptr, nullptr }; +} + +FDisplayClusterDeviceQuadBufferStereoD3D11::~FDisplayClusterDeviceQuadBufferStereoD3D11() +{ +} + +bool FDisplayClusterDeviceQuadBufferStereoD3D11::ShouldUseSeparateRenderTarget() const +{ + return true; +} + +void FDisplayClusterDeviceQuadBufferStereoD3D11::SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy) +{ + FScopeLock lock(&InternalsSyncScope); + UE_LOG(LogDisplayClusterRender, Log, TEXT("Swap sync policy: %d"), (int)policy); + + switch (policy) + { + case EDisplayClusterSwapSyncPolicy::SoftSwapSync: + SwapSyncPolicy = policy; + break; + + default: + // Forward the policy type to the upper level + FDisplayClusterDeviceBase::SetSwapSyncPolicy(policy); + break; + } +} + +bool FDisplayClusterDeviceQuadBufferStereoD3D11::Present(int32& InOutSyncInterval) +{ + // get backbuffer + FD3D11Viewport* viewport = static_cast(CurrentViewport->GetViewportRHI().GetReference()); + +#if !WITH_EDITOR + // Issue frame event + viewport->IssueFrameEvent(); + // Wait until GPU finish last frame commands + viewport->WaitForFrameEventCompletion(); +#endif + + // Sync all nodes + exec_BarrierWait(); + + // present + if (viewport) + { + IDXGISwapChain1* swapchain1 = (IDXGISwapChain1*)viewport->GetSwapChain(); + swapchain1->Present1(GetSwapInt(), 0, &dxgi_present_parameters); + } + + return false; +} + +void FDisplayClusterDeviceQuadBufferStereoD3D11::RenderTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FTexture2DRHIParamRef BackBuffer, FTexture2DRHIParamRef SrcTexture, FVector2D WindowSize) const +{ + check(IsInRenderingThread()); + + //calculate sub regions to copy + const int halfSizeY = BackBuffSize.Y / 2; + + FResolveParams copyParamsLeft; + copyParamsLeft.DestArrayIndex = 0; + copyParamsLeft.SourceArrayIndex = 0; + copyParamsLeft.Rect.X1 = 0; + copyParamsLeft.Rect.X2 = ViewportArea.GetSize().X; + copyParamsLeft.Rect.Y1 = 0; + copyParamsLeft.Rect.Y2 = halfSizeY; + copyParamsLeft.DestRect.X1 = 0; + copyParamsLeft.DestRect.Y1 = 0; + copyParamsLeft.DestRect.X2 = ViewportArea.GetSize().X; + copyParamsLeft.DestRect.Y2 = halfSizeY; + + RHICmdList.CopyToResolveTarget(SrcTexture, BackBuffer, copyParamsLeft); + + FResolveParams copyParamsRight; + copyParamsRight.DestArrayIndex = 1; + copyParamsRight.SourceArrayIndex = 0; + + copyParamsRight.Rect = copyParamsLeft.Rect; + + copyParamsRight.Rect.Y1 = halfSizeY; + copyParamsRight.Rect.Y2 = halfSizeY * 2; + copyParamsRight.DestRect.X1 = 0; + copyParamsRight.DestRect.Y1 = 0; + copyParamsRight.DestRect.X2 = ViewportArea.GetSize().X; + copyParamsRight.DestRect.Y2 = halfSizeY; + + RHICmdList.CopyToResolveTarget(SrcTexture, BackBuffer, copyParamsRight); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.h new file mode 100644 index 000000000000..82d951bd4399 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.h @@ -0,0 +1,30 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterDeviceQuadBufferStereoBase.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + +#include "dxgi1_2.h" + + +/** + * Frame sequenced active stereo (DirectX 11) + */ +class FDisplayClusterDeviceQuadBufferStereoD3D11 : public FDisplayClusterDeviceQuadBufferStereoBase +{ +public: + FDisplayClusterDeviceQuadBufferStereoD3D11(); + virtual ~FDisplayClusterDeviceQuadBufferStereoD3D11(); + +protected: + virtual bool ShouldUseSeparateRenderTarget() const override; + virtual void SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy); + virtual bool Present(int32& InOutSyncInterval) override; + + virtual void RenderTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FTexture2DRHIParamRef BackBuffer, FTexture2DRHIParamRef SrcTexture, FVector2D WindowSize) const override; + +private: + DXGI_PRESENT_PARAMETERS dxgi_present_parameters; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.cpp new file mode 100644 index 000000000000..652f63f2b487 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.cpp @@ -0,0 +1,103 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceQuadBufferStereoD3D12.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + +#include "Misc/DisplayClusterLog.h" + +#include "RHI.h" +#include "RHICommandList.h" + + + +FDisplayClusterDeviceQuadBufferStereoD3D12::FDisplayClusterDeviceQuadBufferStereoD3D12() : + FDisplayClusterDeviceQuadBufferStereoBase() +{ + dxgi_present_parameters = { 0, nullptr, nullptr, nullptr }; +} + +FDisplayClusterDeviceQuadBufferStereoD3D12::~FDisplayClusterDeviceQuadBufferStereoD3D12() +{ +} + +bool FDisplayClusterDeviceQuadBufferStereoD3D12::ShouldUseSeparateRenderTarget() const +{ + return true; +} + +void FDisplayClusterDeviceQuadBufferStereoD3D12::SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy) +{ + FScopeLock lock(&InternalsSyncScope); + UE_LOG(LogDisplayClusterRender, Log, TEXT("Swap sync policy: %d"), (int)policy); + + switch (policy) + { + case EDisplayClusterSwapSyncPolicy::SoftSwapSync: + SwapSyncPolicy = policy; + break; + + default: + // Forward the policy type to the upper level + FDisplayClusterDeviceBase::SetSwapSyncPolicy(policy); + break; + } +} + +bool FDisplayClusterDeviceQuadBufferStereoD3D12::Present(int32& InOutSyncInterval) +{ + FD3D12Viewport* viewport = static_cast(CurrentViewport->GetViewportRHI().GetReference()); + +// This code is not used in editor and required only for packaged builds. To avoid linking issues it won't be used with editor builds. +#if !WITH_EDITOR + // Issue frame event + viewport->IssueFrameEvent(); + // Wait until GPU finish last frame commands + viewport->WaitForFrameEventCompletion(); +#endif + + // Sync all nodes + exec_BarrierWait(); + + // present + IDXGISwapChain1* swapchain1 = (IDXGISwapChain1*)viewport->GetSwapChain(); + swapchain1->Present(GetSwapInt(), 0); + + return false; +} + +void FDisplayClusterDeviceQuadBufferStereoD3D12::RenderTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FTexture2DRHIParamRef BackBuffer, FTexture2DRHIParamRef SrcTexture, FVector2D WindowSize) const +{ + check(IsInRenderingThread()); + + //calculate sub regions to copy + const int halfSizeY = BackBuffSize.Y / 2; + + FResolveParams copyParamsLeft; + copyParamsLeft.DestArrayIndex = 0; + copyParamsLeft.SourceArrayIndex = 0; + copyParamsLeft.Rect.X1 = 0; + copyParamsLeft.Rect.X2 = ViewportArea.GetSize().X; + copyParamsLeft.Rect.Y1 = 0; + copyParamsLeft.Rect.Y2 = halfSizeY; + copyParamsLeft.DestRect.X1 = 0; + copyParamsLeft.DestRect.Y1 = 0; + copyParamsLeft.DestRect.X2 = ViewportArea.GetSize().X; + copyParamsLeft.DestRect.Y2 = halfSizeY; + + RHICmdList.CopyToResolveTarget(SrcTexture, BackBuffer, copyParamsLeft); + + FResolveParams copyParamsRight; + copyParamsRight.DestArrayIndex = 1; + copyParamsRight.SourceArrayIndex = 0; + + copyParamsRight.Rect = copyParamsLeft.Rect; + + copyParamsRight.Rect.Y1 = halfSizeY; + copyParamsRight.Rect.Y2 = halfSizeY*2; + copyParamsRight.DestRect.X1 = 0; + copyParamsRight.DestRect.Y1 = 0; + copyParamsRight.DestRect.X2 = ViewportArea.GetSize().X; + copyParamsRight.DestRect.Y2 = halfSizeY; + + RHICmdList.CopyToResolveTarget(SrcTexture, BackBuffer, copyParamsRight); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.h new file mode 100644 index 000000000000..aa45e8784ca7 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.h @@ -0,0 +1,33 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterDeviceQuadBufferStereoBase.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + +#include "dxgi1_2.h" + + +/** + * Frame sequenced active stereo (DirectX 12) + */ +class FDisplayClusterDeviceQuadBufferStereoD3D12 : public FDisplayClusterDeviceQuadBufferStereoBase +{ +public: + FDisplayClusterDeviceQuadBufferStereoD3D12(); + virtual ~FDisplayClusterDeviceQuadBufferStereoD3D12(); + +protected: + virtual bool ShouldUseSeparateRenderTarget() const override; + virtual void SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy); + virtual bool Present(int32& InOutSyncInterval) override; + + virtual void RenderTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FTexture2DRHIParamRef BackBuffer, FTexture2DRHIParamRef SrcTexture, FVector2D WindowSize) const override; + + //void CopySubregions(bool stereo, FD3D11DeviceContext* d3dContext, ID3D11Texture2D* rttD3DTexture, ID3D11RenderTargetView* leftRTV, ID3D11RenderTargetView* rightRTV); + DXGI_PRESENT_PARAMETERS dxgi_present_parameters; + +private: + //void ClearTargets(FD3D12DeviceContext* d3dContext, ID3D11RenderTargetView* leftRTV, ID3D11RenderTargetView* rightRTV); +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.cpp new file mode 100644 index 000000000000..9836e2dc9967 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.cpp @@ -0,0 +1,363 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceQuadBufferStereoOpenGL.h" + +#include "Render/Devices/DisplayClusterDeviceInternals.h" + +#include "Misc/DisplayClusterLog.h" + +#include + + +FDisplayClusterDeviceQuadBufferStereoOpenGL::FDisplayClusterDeviceQuadBufferStereoOpenGL() : + FDisplayClusterDeviceQuadBufferStereoBase() +{ +#if PLATFORM_WINDOWS + DisplayClusterInitCapabilitiesForGL(); +#endif +} + +FDisplayClusterDeviceQuadBufferStereoOpenGL::~FDisplayClusterDeviceQuadBufferStereoOpenGL() +{ +} + +void FDisplayClusterDeviceQuadBufferStereoOpenGL::SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy) +{ + FScopeLock lock(&InternalsSyncScope); + UE_LOG(LogDisplayClusterRender, Log, TEXT("Swap sync policy: %d"), (int)policy); + + switch (policy) + { + // Policies below are supported by all child implementations + case EDisplayClusterSwapSyncPolicy::SoftSwapSync: + // falls through + case EDisplayClusterSwapSyncPolicy::NvSwapSync: + { + SwapSyncPolicy = policy; + break; + } + + default: + // Forward the policy type to the upper level + FDisplayClusterDeviceBase::SetSwapSyncPolicy(policy); + break; + } +} + +////////////////////////////////////////////////////////////////////////////////////////////// +// Windows implementation +////////////////////////////////////////////////////////////////////////////////////////////// +#if PLATFORM_WINDOWS +bool FDisplayClusterDeviceQuadBufferStereoOpenGL::Present(int32& InOutSyncInterval) +{ + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("FDisplayClusterDeviceQuadBufferStereoOpenGL::Present")); + + const int halfSizeY = BackBuffSize.Y / 2; + int dstX1 = 0; + int dstX2 = BackBuffSize.X; + + // Convert to left bottom origin and flip Y + int dstY1 = ViewportSize.Y; + int dstY2 = 0; + + if (bFlipHorizontal) + { + std::swap(dstX1, dstX2); + } + + if (bFlipVertical) + { + std::swap(dstY1, dstY2); + } + + + FOpenGLViewport* pOglViewport = static_cast(CurrentViewport->GetViewportRHI().GetReference()); + check(pOglViewport); + FPlatformOpenGLContext* const pContext = pOglViewport->GetGLContext(); + check(pContext && pContext->DeviceContext); + + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, pContext->ViewportFramebuffer); + glReadBuffer(GL_COLOR_ATTACHMENT0); + + glDrawBuffer(GL_BACK_LEFT); + glBlitFramebuffer( + 0, 0, BackBuffSize.X, halfSizeY, + dstX1, dstY1, dstX2, dstY2, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + + glDrawBuffer(GL_BACK_RIGHT); + glBlitFramebuffer( + 0, halfSizeY, BackBuffSize.X, BackBuffSize.Y, + dstX1, dstY1, dstX2, dstY2, + GL_COLOR_BUFFER_BIT, + GL_NEAREST); + + // Perform buffers swap logic + SwapBuffers(pOglViewport, InOutSyncInterval); + REPORT_GL_END_BUFFER_EVENT_FOR_FRAME_DUMP(); + + return false; +} +#endif + + +////////////////////////////////////////////////////////////////////////////////////////////// +// Linux implementation +////////////////////////////////////////////////////////////////////////////////////////////// +#if PLATFORM_LINUX +//@todo: Implementation for Linux +bool FDisplayClusterDeviceQuadBufferStereoOpenGL::Present(int32& InOutSyncInterval) +{ + // Forward to default implementation (should be a black screen) + return FDisplayClusterDeviceBase::Present(InOutSyncInterval); +} +#endif + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterDeviceBaseComplex +////////////////////////////////////////////////////////////////////////////////////////////// +//@todo: this should be combined somehow with FDisplayClusterDeviceBase::WaitForBufferSwapSync. It seems +// they both have the same purpose but there is a GL viewport. +void FDisplayClusterDeviceQuadBufferStereoOpenGL::SwapBuffers(FOpenGLViewport* pOglViewport, int32& InOutSyncInterval) +{ + check(pOglViewport && pOglViewport->GetGLContext() && pOglViewport->GetGLContext()->DeviceContext); + { + /////////////////////////////////////////////// + // Perform swap policy + UE_LOG(LogDisplayClusterRender, Verbose, TEXT("Exec swap policy: %d"), (int)SwapSyncPolicy); + switch (SwapSyncPolicy) + { + case EDisplayClusterSwapSyncPolicy::None: + internal_SwapBuffersPolicyNone(pOglViewport); + break; + + case EDisplayClusterSwapSyncPolicy::SoftSwapSync: + internal_SwapBuffersPolicySoftSwapSync(pOglViewport); + break; + + case EDisplayClusterSwapSyncPolicy::NvSwapSync: + internal_SwapBuffersPolicyNvSwapSync(pOglViewport); + break; + + default: + UE_LOG(LogDisplayClusterRender, Error, TEXT("Unknown swap sync policy: %d"), (int)SwapSyncPolicy); + break; + } + } +} + + +#if PLATFORM_WINDOWS +void FDisplayClusterDeviceQuadBufferStereoOpenGL::internal_SwapBuffersPolicyNone(FOpenGLViewport* pOglViewport) +{ + { + /////////////////////////////////////////////// + // Swap buffers + const double wtB = FPlatformTime::Seconds(); + ::SwapBuffers(pOglViewport->GetGLContext()->DeviceContext); + const double wtA = FPlatformTime::Seconds(); + + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP bef: %lf"), wtB); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP aft: %lf"), wtA); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP diff: %lf"), wtA - wtB); + } +} + +void FDisplayClusterDeviceQuadBufferStereoOpenGL::internal_SwapBuffersPolicySoftSwapSync(FOpenGLViewport* pOglViewport) +{ + static double lastSwapBuffersTime = 0; + +// This code is not used in editor and required only for packaged builds. To avoid linking issues it won't be used with editor builds. +#if !WITH_EDITOR + { + // Issue frame event + pOglViewport->IssueFrameEvent(); + + // Wait until GPU finish last frame commands + const double wtB = FPlatformTime::Seconds(); + pOglViewport->WaitForFrameEventCompletion(); + const double wtA = FPlatformTime::Seconds(); + + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT EVENT bef: %lf"), wtB); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT EVENT aft: %lf"), wtA); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT EVENT diff: %lf"), wtA - wtB); + } +#endif + + // Sync all nodes + exec_BarrierWait(); + + // Update swap interval right before swap buffers call + UpdateSwapInterval(GetSwapInt()); + + { + /////////////////////////////////////////////// + // Swap buffers + const double wtB = FPlatformTime::Seconds(); + ::SwapBuffers(pOglViewport->GetGLContext()->DeviceContext); + const double wtA = FPlatformTime::Seconds(); + + lastSwapBuffersTime = wtA; + + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP bef: %lf"), wtB); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP aft: %lf"), wtA); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP diff: %lf"), wtA - wtB); + } +} + +void FDisplayClusterDeviceQuadBufferStereoOpenGL::internal_SwapBuffersPolicyNvSwapSync(FOpenGLViewport* pOglViewport) +{ + // Use barrier once during NV barriers initialization + if (bNvSwapInitialized == false) + { + // Use render barrier to guaranty that all nv barriers are initialized simultaneously + exec_BarrierWait(); + bNvSwapInitialized = InitializeNvidiaSwapLock(); + } + + { + /////////////////////////////////////////////// + // Swap buffers + const double wtB = FPlatformTime::Seconds(); + ::SwapBuffers(pOglViewport->GetGLContext()->DeviceContext); + const double wtA = FPlatformTime::Seconds(); + + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP bef: %lf"), wtB); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP aft: %lf"), wtA); + UE_LOG(LogDisplayClusterRender, VeryVerbose, TEXT("WAIT SWAP diff: %lf"), wtA - wtB); + } +} +#endif // PLATFORM_WINDOWS + +#if PLATFORM_LINUX +void FDisplayClusterDeviceQuadBufferStereoOpenGL::internal_SwapBuffersPolicyNone(FOpenGLViewport* pOglViewport) +{ + //@todo: Implementation for Linux +} + +void FDisplayClusterDeviceQuadBufferStereoOpenGL::internal_SwapBuffersPolicySoftSwapSync(FOpenGLViewport* pOglViewport) +{ + //@todo: Implementation for Linux +} + +void FDisplayClusterDeviceQuadBufferStereoOpenGL::internal_SwapBuffersPolicyNvSwapSync(FOpenGLViewport* pOglViewport) +{ + //@todo: Implementation for Linux +} +#endif // PLATFORM_LINUX + +void FDisplayClusterDeviceQuadBufferStereoOpenGL::UpdateSwapInterval(int32 swapInt) const +{ +#if PLATFORM_WINDOWS + /* + https://www.opengl.org/registry/specs/EXT/wgl_swap_control.txt + wglSwapIntervalEXT specifies the minimum number of video frame periods + per buffer swap for the window associated with the current context. + The interval takes effect when SwapBuffers or wglSwapLayerBuffer + is first called subsequent to the wglSwapIntervalEXT call. + + The parameter specifies the minimum number of video frames + that are displayed before a buffer swap will occur. + + A video frame period is the time required by the monitor to display a + full frame of video data. In the case of an interlaced monitor, + this is typically the time required to display both the even and odd + fields of a frame of video data. An interval set to a value of 2 + means that the color buffers will be swapped at most every other video + frame. + + If is set to a value of 0, buffer swaps are not synchron- + ized to a video frame. The value is silently clamped to + the maximum implementation-dependent value supported before being + stored. + + The swap interval is not part of the render context state. It cannot + be pushed or popped. The current swap interval for the window + associated with the current context can be obtained by calling + wglGetSwapIntervalEXT. The default swap interval is 1. + */ + + // Perform that each frame + if (!DisplayCluster_wglSwapIntervalEXT_ProcAddress(swapInt)) + UE_LOG(LogDisplayClusterRender, Error, TEXT("Couldn't set swap interval: %d"), swapInt); + +#elif + //@todo: Implementation for Linux +#endif +} + + +#if PLATFORM_WINDOWS +bool FDisplayClusterDeviceQuadBufferStereoOpenGL::InitializeNvidiaSwapLock() +{ + if (bNvSwapInitialized) + { + return true; + } + + if (!DisplayCluster_wglJoinSwapGroupNV_ProcAddress || !DisplayCluster_wglBindSwapBarrierNV_ProcAddress) + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("Group/Barrier functions not available")); + return false; + } + + if (!CurrentViewport) + { + UE_LOG(LogDisplayClusterRender, Warning, TEXT("Viewport RHI hasn't been initialized yet")) + return false; + } + + FOpenGLViewport* pOglViewport = static_cast(CurrentViewport->GetViewportRHI().GetReference()); + check(pOglViewport); + FPlatformOpenGLContext* const pContext = pOglViewport->GetGLContext(); + check(pContext && pContext->DeviceContext); + + GLuint maxGroups = 0; + GLuint maxBarriers = 0; + + if (!DisplayCluster_wglQueryMaxSwapGroupsNV_ProcAddress(pContext->DeviceContext, &maxGroups, &maxBarriers)) + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("Couldn't query gr/br limits: %d"), glGetError()); + return false; + } + + UE_LOG(LogDisplayClusterRender, Log, TEXT("max_groups=%d max_barriers=%d"), (int)maxGroups, (int)maxBarriers); + + if (!(maxGroups > 0 && maxBarriers > 0)) + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("There are no available groups or barriers")); + return false; + } + + if (!DisplayCluster_wglJoinSwapGroupNV_ProcAddress(pContext->DeviceContext, 1)) + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("Couldn't join swap group: %d"), glGetError()); + return false; + } + else + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Successfully joined the swap group: 1")); + } + + if (!DisplayCluster_wglBindSwapBarrierNV_ProcAddress(1, 1)) + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("Couldn't bind to swap barrier: %d"), glGetError()); + return false; + } + else + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Successfully binded to the swap barrier: 1")); + } + + return true; +} +#elif PLATFORM_LINUX +bool FDisplayClusterDeviceQuadBufferStereoOpenGL::InitializeNvidiaSwapLock() +{ + //@todo: Implementation for Linux + return false; +} +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.h new file mode 100644 index 000000000000..f9c9157403b7 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.h @@ -0,0 +1,37 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterDeviceQuadBufferStereoBase.h" +#include "Render/Devices/DisplayClusterDeviceInternals.h" + + +/** + * Frame sequenced active stereo (OpenGL 3 and 4) + */ +class FDisplayClusterDeviceQuadBufferStereoOpenGL : public FDisplayClusterDeviceQuadBufferStereoBase +{ +public: + FDisplayClusterDeviceQuadBufferStereoOpenGL(); + virtual ~FDisplayClusterDeviceQuadBufferStereoOpenGL(); + +protected: + virtual void SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy); + virtual bool Present(int32& InOutSyncInterval) override; + void SwapBuffers(FOpenGLViewport* pOglViewport, int32& InOutSyncInterval); + +private: + // Set up swap interval for upcoming buffer swap + void UpdateSwapInterval(int32 swapInt) const; + // Joins swap groups and binds to a swap barrier + bool InitializeNvidiaSwapLock(); + + // Implementation of swap policies + void internal_SwapBuffersPolicyNone(FOpenGLViewport* pOglViewport); + void internal_SwapBuffersPolicySoftSwapSync(FOpenGLViewport* pOglViewport); + void internal_SwapBuffersPolicyNvSwapSync(FOpenGLViewport* pOglViewport); + +private: + // State of nv_swap initialization + bool bNvSwapInitialized = false; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.cpp new file mode 100644 index 000000000000..c751f0488509 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.cpp @@ -0,0 +1,32 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceSideBySide.h" + + +FDisplayClusterDeviceSideBySide::FDisplayClusterDeviceSideBySide() +{ +} + +FDisplayClusterDeviceSideBySide::~FDisplayClusterDeviceSideBySide() +{ +} + + +void FDisplayClusterDeviceSideBySide::AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const +{ + FDisplayClusterDeviceBase::AdjustViewRect(StereoPass, X, Y, SizeX, SizeY); + + SizeX /= 2; + if (StereoPass == EStereoscopicPass::eSSP_RIGHT_EYE) + { + X += SizeX; + } +} + +bool FDisplayClusterDeviceSideBySide::Present(int32& InOutSyncInterval) +{ + // Wait for swap sync + WaitForBufferSwapSync(InOutSyncInterval); + + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.h new file mode 100644 index 000000000000..cff14d021fc3 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.h @@ -0,0 +1,26 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Render/Devices/DisplayClusterDeviceBase.h" + + +/** + * Side-by-side passive stereoscopic device + */ +class FDisplayClusterDeviceSideBySide : public FDisplayClusterDeviceBase +{ +public: + FDisplayClusterDeviceSideBySide(); + virtual ~FDisplayClusterDeviceSideBySide(); + +protected: + virtual void AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const override; +protected: + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // FRHICustomPresent + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Present(int32& InOutSyncInterval) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.cpp new file mode 100644 index 000000000000..8ee0c5b5e5c3 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.cpp @@ -0,0 +1,31 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterDeviceTopBottom.h" + + +FDisplayClusterDeviceTopBottom::FDisplayClusterDeviceTopBottom() +{ +} + +FDisplayClusterDeviceTopBottom::~FDisplayClusterDeviceTopBottom() +{ +} + + +void FDisplayClusterDeviceTopBottom::AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const +{ + SizeY /= 2; + if (StereoPass == EStereoscopicPass::eSSP_RIGHT_EYE) + { + Y = SizeY; + } +} + + +bool FDisplayClusterDeviceTopBottom::Present(int32& InOutSyncInterval) +{ + // Wait for swap sync + WaitForBufferSwapSync(InOutSyncInterval); + + return true; +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.h new file mode 100644 index 000000000000..63326e4f7bac --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.h @@ -0,0 +1,25 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Render/Devices/DisplayClusterDeviceBase.h" + + +/** + * Top-bottom passive stereoscopic device + */ +class FDisplayClusterDeviceTopBottom : public FDisplayClusterDeviceBase +{ +public: + FDisplayClusterDeviceTopBottom(); + virtual ~FDisplayClusterDeviceTopBottom(); + +protected: + virtual void AdjustViewRect(enum EStereoscopicPass StereoPass, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const override; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // FRHICustomPresent + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Present(int32& InOutSyncInterval) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/DisplayClusterRenderManager.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/DisplayClusterRenderManager.cpp new file mode 100644 index 000000000000..9274b336fc29 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/DisplayClusterRenderManager.cpp @@ -0,0 +1,254 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "Render/DisplayClusterRenderManager.h" +#include "Config/IPDisplayClusterConfigManager.h" + +#include "Engine/GameEngine.h" +#include "Misc/DisplayClusterLog.h" +#include "DisplayClusterStrings.h" +#include "DisplayClusterOperationMode.h" + +#include "Render/Devices/Debug/DisplayClusterDeviceDebug.h" +#include "Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicOpenGL.h" +#include "Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D11.h" +#include "Render/Devices/Monoscopic/DisplayClusterDeviceMonoscopicD3D12.h" +#include "Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoOpenGL.h" +#include "Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D11.h" +#include "Render/Devices/QuadBufferStereo/DisplayClusterDeviceQuadBufferStereoD3D12.h" +#include "Render/Devices/SideBySide/DisplayClusterDeviceSideBySide.h" +#include "Render/Devices/TopBottom/DisplayClusterDeviceTopBottom.h" + + +FDisplayClusterRenderManager::FDisplayClusterRenderManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); +} + +FDisplayClusterRenderManager::~FDisplayClusterRenderManager() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IPDisplayClusterManager +////////////////////////////////////////////////////////////////////////////////////////////// +bool FDisplayClusterRenderManager::Init(EDisplayClusterOperationMode OperationMode) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); + + CurrentOperationMode = OperationMode; + + return true; +} + +void FDisplayClusterRenderManager::Release() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); + + //@note: No need to release our device. It will be released in safe way by TSharedPtr. +} + +bool FDisplayClusterRenderManager::StartSession(const FString& configPath, const FString& nodeId) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); + + ConfigPath = configPath; + ClusterNodeId = nodeId; + + if (!GEngine) + { +#if !WITH_EDITOR + UE_LOG(LogDisplayClusterRender, Error, TEXT("GEngine variable not set")); +#endif + return false; + } + + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating stereo device...")); + + FDisplayClusterDeviceBase* pDev = CreateStereoDevice(); + if (pDev) + { + // Store ptr for internal usage + Device = static_cast(pDev); + // Set new device in the engine + GEngine->StereoRenderingDevice = TSharedPtr(static_cast(pDev)); + } + + // When session is starting in Editor the device won't be initialized so we avoid nullptr access here. + return (Device ? static_cast(Device)->Initialize() : true); +} + +void FDisplayClusterRenderManager::EndSession() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// IDisplayClusterRenderManager +////////////////////////////////////////////////////////////////////////////////////////////// +IDisplayClusterStereoDevice* FDisplayClusterRenderManager::GetStereoDevice() const +{ + return Device; +} + + +////////////////////////////////////////////////////////////////////////////////////////////// +// FDisplayClusterRenderManager +////////////////////////////////////////////////////////////////////////////////////////////// +FDisplayClusterDeviceBase* FDisplayClusterRenderManager::CreateStereoDevice() +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); + + FDisplayClusterDeviceBase* pDevice = nullptr; + + if (CurrentOperationMode == EDisplayClusterOperationMode::Cluster || CurrentOperationMode == EDisplayClusterOperationMode::Standalone) + { + if (GDynamicRHI == nullptr) + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("GDynamicRHI is null. Cannot detect RHI name.")); + return nullptr; + } + + // Depending on RHI name we will be using non-RHI-agnostic rendering devices + const FString RHIName = GDynamicRHI->GetName(); + UE_LOG(LogDisplayClusterRender, Log, TEXT("Running %s RHI"), *RHIName); + + // Debug stereo device is RHI agnostic + if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::dev::Debug)) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating debug stereo device...")); + pDevice = new FDisplayClusterDeviceDebug; + } + // Side-by-side device is RHI agnostic + else if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::dev::SbS)) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating side-by-side stereo device...")); + pDevice = new FDisplayClusterDeviceSideBySide; + } + // Top-bottom device is RHI agnostic + else if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::dev::TB)) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating top-bottom stereo device...")); + pDevice = new FDisplayClusterDeviceTopBottom; + } + // Quad buffer stereo + else if (FParse::Param(FCommandLine::Get(), DisplayClusterStrings::args::dev::QBS)) + { + if (RHIName.Compare(DisplayClusterStrings::rhi::OpenGL, ESearchCase::IgnoreCase) == 0) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating OpenGL quad buffer stereo device...")); + pDevice = new FDisplayClusterDeviceQuadBufferStereoOpenGL; + } + else if (RHIName.Compare(DisplayClusterStrings::rhi::D3D11, ESearchCase::IgnoreCase) == 0) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating D3D11 quad buffer stereo device...")); + pDevice = new FDisplayClusterDeviceQuadBufferStereoD3D11; + } + else if (RHIName.Compare(DisplayClusterStrings::rhi::D3D12, ESearchCase::IgnoreCase) == 0) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating D3D12 quad buffer stereo device...")); + pDevice = new FDisplayClusterDeviceQuadBufferStereoD3D12; + } + } + // Monoscopic + else //if (FParse::Param(FCommandLine::Get(), DisplayClusterConstants::args::dev::Mono)) + { + if (RHIName.Compare(DisplayClusterStrings::rhi::OpenGL, ESearchCase::IgnoreCase) == 0) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating OpenGL monoscopic device...")); + pDevice = new FDisplayClusterDeviceMonoscopicOpenGL; + } + else if (RHIName.Compare(DisplayClusterStrings::rhi::D3D11, ESearchCase::IgnoreCase) == 0) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating DX11 monoscopic device...")); + pDevice = new FDisplayClusterDeviceMonoscopicD3D11; + } + else if (RHIName.Compare(DisplayClusterStrings::rhi::D3D12, ESearchCase::IgnoreCase) == 0) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Instantiating DX12 monoscopic device...")); + pDevice = new FDisplayClusterDeviceMonoscopicD3D12; + } + } + + if (pDevice == nullptr) + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("No stereo device created")); + } + } + else if (CurrentOperationMode == EDisplayClusterOperationMode::Editor) + { + // No stereo in editor + UE_LOG(LogDisplayClusterRender, Warning, TEXT("DisplayCluster stereo devices for editor mode are not allowed currently")); + } + else if (CurrentOperationMode == EDisplayClusterOperationMode::Disabled) + { + // Stereo device is not needed + UE_LOG(LogDisplayClusterRender, Log, TEXT("No need to instantiate stereo device")); + } + else + { + UE_LOG(LogDisplayClusterRender, Warning, TEXT("Unknown operation mode")); + } + + return pDevice; +} + +void FDisplayClusterRenderManager::PreTick(float DeltaSeconds) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); + + // Adjust position and size of game window to match window config. + // This needs to happen after UGameEngine::SwitchGameWindowToUseGameViewport + // is called. In practice that happens from FEngineLoop::Init after a call + // to UGameEngine::Start - therefore this is done in PreTick on the first frame. + if (!bWindowAdjusted) + { + bWindowAdjusted = true; + +//#ifdef DISPLAY_CLUSTER_USE_DEBUG_STANDALONE_CONFIG +#if 0 + if (GDisplayCluster->GetPrivateConfigMgr()->IsRunningDebugAuto()) + { + UE_LOG(LogDisplayClusterRender, Log, TEXT("Running in debug auto mode. Adjusting window...")); + ResizeWindow(DisplayClusterConstants::misc::DebugAutoWinX, DisplayClusterConstants::misc::DebugAutoWinY, DisplayClusterConstants::misc::DebugAutoResX, DisplayClusterConstants::misc::DebugAutoResY); + return; + } +#endif + + if (FParse::Param(FCommandLine::Get(), TEXT("windowed"))) + { + int32 WinX = 0; + int32 WinY = 0; + int32 ResX = 0; + int32 ResY = 0; + + if (FParse::Value(FCommandLine::Get(), TEXT("WinX="), WinX) && + FParse::Value(FCommandLine::Get(), TEXT("WinY="), WinY) && + FParse::Value(FCommandLine::Get(), TEXT("ResX="), ResX) && + FParse::Value(FCommandLine::Get(), TEXT("ResY="), ResY)) + { + ResizeWindow(WinX, WinY, ResX, ResY); + } + else + { + UE_LOG(LogDisplayClusterRender, Error, TEXT("Wrong window pos/size arguments")); + } + } + } +} + +void FDisplayClusterRenderManager::ResizeWindow(int32 WinX, int32 WinY, int32 ResX, int32 ResY) +{ + DISPLAY_CLUSTER_FUNC_TRACE(LogDisplayClusterRender); + + UGameEngine* engine = Cast(GEngine); + TSharedPtr window = engine->GameViewportWindow.Pin(); + check(window.IsValid()); + + UE_LOG(LogDisplayClusterRender, Log, TEXT("Adjusting game window: pos [%d, %d], size [%d x %d]"), WinX, WinY, ResX, ResY); + + // Adjust window position/size + window->ReshapeWindow(FVector2D(WinX, WinY), FVector2D(ResX, ResY)); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/DisplayClusterRenderManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/DisplayClusterRenderManager.h new file mode 100644 index 000000000000..609819d287f8 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/DisplayClusterRenderManager.h @@ -0,0 +1,56 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IPDisplayClusterRenderManager.h" + +class FDisplayClusterDeviceBase; +enum class EDisplayClusterOperationMode; + + +/** + * Render manager. Responsible for anything related to a visual part. + */ +class FDisplayClusterRenderManager + : public IPDisplayClusterRenderManager +{ +public: + FDisplayClusterRenderManager(); + virtual ~FDisplayClusterRenderManager(); + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual bool Init(EDisplayClusterOperationMode OperationMode) override; + virtual void Release() override; + virtual bool StartSession(const FString& configPath, const FString& nodeId) override; + virtual void EndSession() override; + virtual void PreTick(float DeltaSeconds) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterRenderManager + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual IDisplayClusterStereoDevice* GetStereoDevice() const override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IPDisplayClusterRenderManager + ////////////////////////////////////////////////////////////////////////////////////////////// + +private: + FDisplayClusterDeviceBase* CreateStereoDevice(); + void ResizeWindow(int32 WinX, int32 WinY, int32 ResX, int32 ResY); + +private: + EDisplayClusterOperationMode CurrentOperationMode; + FString ConfigPath; + FString ClusterNodeId; + + // Interface pointer to eliminate type casting + IDisplayClusterStereoDevice* Device = nullptr; + bool bWindowAdjusted = false; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/IPDisplayClusterRenderManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/IPDisplayClusterRenderManager.h new file mode 100644 index 000000000000..6a10bc111bbd --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Private/Render/IPDisplayClusterRenderManager.h @@ -0,0 +1,19 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Render/IDisplayClusterRenderManager.h" +#include "IPDisplayClusterManager.h" + + +/** + * Render manager interface + */ +struct IPDisplayClusterRenderManager + : public IDisplayClusterRenderManager + , public IPDisplayClusterManager +{ + virtual ~IPDisplayClusterRenderManager() + { } + +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Blueprints/DisplayClusterBlueprintLib.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Blueprints/DisplayClusterBlueprintLib.h new file mode 100644 index 000000000000..f2bced358dcc --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Blueprints/DisplayClusterBlueprintLib.h @@ -0,0 +1,24 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "Blueprints/IDisplayClusterBlueprintAPI.h" +#include "DisplayClusterBlueprintLib.generated.h" + + +/** + * Blueprint API function library + */ +UCLASS() +class UDisplayClusterBlueprintLib + : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + +public: + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get plugin API"), Category = "DisplayCluster") + static void GetAPI(TScriptInterface& OutAPI); +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Blueprints/IDisplayClusterBlueprintAPI.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Blueprints/IDisplayClusterBlueprintAPI.h new file mode 100644 index 000000000000..4be69ec9eb72 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Blueprints/IDisplayClusterBlueprintAPI.h @@ -0,0 +1,189 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Interface.h" +#include "IDisplayClusterBlueprintAPI.generated.h" + + +UINTERFACE(meta = (CannotImplementInterfaceInBlueprint)) +class DISPLAYCLUSTER_API UDisplayClusterBlueprintAPI : public UInterface +{ + GENERATED_BODY() +}; + + +/** + * Blueprint API interface + */ +class DISPLAYCLUSTER_API IDisplayClusterBlueprintAPI +{ + GENERATED_BODY() + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Cluster API + ////////////////////////////////////////////////////////////////////////////////////////////// + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is master node"), Category = "DisplayCluster|Cluster") + virtual bool IsMaster() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is slave node"), Category = "DisplayCluster|Cluster") + virtual bool IsSlave() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is cluster mode"), Category = "DisplayCluster|Cluster") + virtual bool IsCluster() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is standalone mode"), Category = "DisplayCluster|Cluster") + virtual bool IsStandalone() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get node ID"), Category = "DisplayCluster|Cluster") + virtual FString GetNodeId() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get nodes amount"), Category = "DisplayCluster|Cluster") + virtual int32 GetNodesAmount() = 0; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Config API + ////////////////////////////////////////////////////////////////////////////////////////////// + + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Game API + ////////////////////////////////////////////////////////////////////////////////////////////// + // Root + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get root"), Category = "DisplayCluster|Game") + virtual ADisplayClusterPawn* GetRoot() = 0; + + // Screens + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get active screen"), Category = "DisplayCluster|Game") + virtual UDisplayClusterScreenComponent* GetActiveScreen() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get screen by ID"), Category = "DisplayCluster|Game") + virtual UDisplayClusterScreenComponent* GetScreenById(const FString& id) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get all screens"), Category = "DisplayCluster|Game") + virtual TArray GetAllScreens() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get amount of screens"), Category = "DisplayCluster|Game") + virtual int32 GetScreensAmount() = 0; + + // Cameras + /* + virtual UDisplayClusterCameraComponent* GetActiveCamera() const = 0; + virtual UDisplayClusterCameraComponent* GetCameraById(const FString& id) const = 0; + virtual TArray GetAllCameras() const = 0; + virtual int32 GetCamerasAmount() const = 0; + virtual void SetActiveCamera(int32 idx) = 0; + virtual void SetActiveCamera(const FString& id) = 0; + */ + + // Nodes + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get node by ID"), Category = "DisplayCluster|Game") + virtual UDisplayClusterSceneComponent* GetNodeById(const FString& id) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get all nodes"), Category = "DisplayCluster|Game") + virtual TArray GetAllNodes() = 0; + + // Navigation + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get translation direction component"), Category = "DisplayCluster|Game") + virtual USceneComponent* GetTranslationDirectionComponent() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set translation direction component"), Category = "DisplayCluster|Game") + virtual void SetTranslationDirectionComponent(USceneComponent* pComp) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set translation direction component by ID"), Category = "DisplayCluster|Game") + virtual void SetTranslationDirectionComponentId(const FString& id) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get rotate around component"), Category = "DisplayCluster|Game") + virtual USceneComponent* GetRotateAroundComponent() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set rotate around component"), Category = "DisplayCluster|Game") + virtual void SetRotateAroundComponent(USceneComponent* pComp) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set rotate around component by ID"), Category = "DisplayCluster|Game") + virtual void SetRotateAroundComponentId(const FString& id) = 0; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Input API + ////////////////////////////////////////////////////////////////////////////////////////////// + // Device information + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get amount of VRPN axis devices"), Category = "DisplayCluster|Input") + virtual int32 GetAxisDeviceAmount() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get amount of VRPN button devices"), Category = "DisplayCluster|Input") + virtual int32 GetButtonDeviceAmount() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get amount of VRPN tracker devices"), Category = "DisplayCluster|Input") + virtual int32 GetTrackerDeviceAmount() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get IDs of VRPN axis devices"), Category = "DisplayCluster|Input") + virtual bool GetAxisDeviceIds(TArray& IDs) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get IDs of VRPN button devices"), Category = "DisplayCluster|Input") + virtual bool GetButtonDeviceIds(TArray& IDs) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get IDs of VRPN tracker devices"), Category = "DisplayCluster|Input") + virtual bool GetTrackerDeviceIds(TArray& IDs) = 0; + + // Buttons + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get VRPN button state"), Category = "DisplayCluster|Input") + virtual void GetButtonState(const FString& DeviceId, uint8 DeviceChannel, bool& CurState, bool& IsChannelAvailable) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is VRPN button pressed"), Category = "DisplayCluster|Input") + virtual void IsButtonPressed(const FString& DeviceId, uint8 DeviceChannel, bool& CurPressed, bool& IsChannelAvailable) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Is VRPN button released"), Category = "DisplayCluster|Input") + virtual void IsButtonReleased(const FString& DeviceId, uint8 DeviceChannel, bool& CurReleased, bool& IsChannelAvailable) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Was VRPN button pressed"), Category = "DisplayCluster|Input") + virtual void WasButtonPressed(const FString& DeviceId, uint8 DeviceChannel, bool& WasPressed, bool& IsChannelAvailable) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Was VRPN button released"), Category = "DisplayCluster|Input") + virtual void WasButtonReleased(const FString& DeviceId, uint8 DeviceChannel, bool& WasReleased, bool& IsChannelAvailable) = 0; + + // Axes + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get VRPN axis value"), Category = "DisplayCluster|Input") + virtual void GetAxis(const FString& DeviceId, uint8 DeviceChannel, float& Value, bool& IsAvailable) = 0; + + // Trackers + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get VRPN tracker location"), Category = "DisplayCluster|Input") + virtual void GetTrackerLocation(const FString& DeviceId, uint8 DeviceChannel, FVector& Location, bool& IsChannelAvailable) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get VRPN tracker rotation (as quaternion)"), Category = "DisplayCluster|Input") + virtual void GetTrackerQuat(const FString& DeviceId, uint8 DeviceChannel, FQuat& Rotation, bool& IsChannelAvailable) = 0; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // Render API + ////////////////////////////////////////////////////////////////////////////////////////////// + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set interpuppillary distance"), Category = "DisplayCluster|Render") + virtual void SetInterpupillaryDistance(float dist) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get interpuppillary distance"), Category = "DisplayCluster|Render") + virtual float GetInterpupillaryDistance() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set eye swap"), Category = "DisplayCluster|Render") + virtual void SetEyesSwap(bool swap) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get eye swap"), Category = "DisplayCluster|Render") + virtual bool GetEyesSwap() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Toggle eye swap"), Category = "DisplayCluster|Render") + virtual bool ToggleEyesSwap() = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set output flip"), Category = "DisplayCluster|Render") + virtual void SetOutputFlip(bool flipH, bool flipV) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get output flip"), Category = "DisplayCluster|Render") + virtual void GetOutputFlip(bool& flipH, bool& flipV) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get near and far clipping distance"), Category = "DisplayCluster|Render") + virtual void GetCullingDistance(float& NearClipPlane, float& FarClipPlane) = 0; + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set near and far clipping distance"), Category = "DisplayCluster|Render") + virtual void SetCullingDistance(float NearClipPlane, float FarClipPlane) = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Cluster/IDisplayClusterClusterManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Cluster/IDisplayClusterClusterManager.h new file mode 100644 index 000000000000..9674d4772314 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Cluster/IDisplayClusterClusterManager.h @@ -0,0 +1,22 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + + +/** + * Public cluster manager interface + */ +struct IDisplayClusterClusterManager +{ + virtual ~IDisplayClusterClusterManager() + { } + + virtual bool IsMaster() const = 0; + virtual bool IsSlave() const = 0; + virtual bool IsStandalone() const = 0; + virtual bool IsCluster() const = 0; + virtual FString GetNodeId() const = 0; + virtual uint32 GetNodesAmount() const = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Cluster/IDisplayClusterClusterSyncObject.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Cluster/IDisplayClusterClusterSyncObject.h new file mode 100644 index 000000000000..8fdfcabbfd83 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Cluster/IDisplayClusterClusterSyncObject.h @@ -0,0 +1,23 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDisplayClusterStringSerializable.h" + + +/** + * Synchronizable object interface + */ +struct IDisplayClusterClusterSyncObject + : public IDisplayClusterStringSerializable +{ + virtual ~IDisplayClusterClusterSyncObject() + { } + + // Unique ID of synced object + virtual FString GetSyncId() const = 0; + // Check if object has changed since last ClearDirty call + virtual bool IsDirty() const = 0; + // Cleans dirty flag making it 'not changed yet' + virtual void ClearDirty() = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Config/DisplayClusterConfigTypes.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Config/DisplayClusterConfigTypes.h new file mode 100644 index 000000000000..e059b81e2e9d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Config/DisplayClusterConfigTypes.h @@ -0,0 +1,169 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IDisplayClusterStringSerializable.h" + + +////////////////////////////////////////////////////////////////////////////////////////////// +// Base interface for config data holders +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigBase : public IDisplayClusterStringSerializable +{ + virtual ~FDisplayClusterConfigBase() + { } + + // Prints in human readable format + virtual FString ToString() const + { return FString("[]"); } + + // Currently no need to serialize the data + virtual FString SerializeToString() const override final + { return FString(); } + + // Deserialization from config file + virtual bool DeserializeFromString(const FString& line) override + { return true; } +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Cluster node configuration (separate application) +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigClusterNode : public FDisplayClusterConfigBase +{ + FString Id; + FString Addr; + FString ScreenId; + FString ViewportId; + bool IsMaster = false; + int32 Port_CS = -1; + int32 Port_SS = -1; + bool SoundEnabled = false; + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Viewport configuration +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigViewport : public FDisplayClusterConfigBase +{ + FString Id; + FIntPoint Loc = FIntPoint::ZeroValue; + FIntPoint Size = FIntPoint::ZeroValue; + bool FlipHorizontal = false; + bool FlipVertical = false; + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Scene node configuration (DisplayCluster hierarchy is built from such nodes) +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigSceneNode : public FDisplayClusterConfigBase +{ + FString Id; + FString ParentId; + FVector Loc = FVector::ZeroVector; + FRotator Rot = FRotator::ZeroRotator; + FString TrackerId; + int32 TrackerCh = -1; + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Projection screen configuration (used for asymmetric frustum calculation) +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigScreen : public FDisplayClusterConfigSceneNode +{ + FVector2D Size = FVector2D::ZeroVector; + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Camera configuration (DisplayCluster camera) +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigCamera : public FDisplayClusterConfigSceneNode +{ + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Input device configuration (VRPN and other possible devices) +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigInput : public FDisplayClusterConfigBase +{ + FString Id; + FString Type; + FString Params; + TMap ChMap; + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// General DisplayCluster configuration +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigGeneral : public FDisplayClusterConfigBase +{ + int32 SwapSyncPolicy = 0; + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Render configuration +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigRender : public FDisplayClusterConfigBase +{ + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Stereo configuration +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigStereo : public FDisplayClusterConfigBase +{ + bool EyeSwap = false; + float EyeDist = 0.064f; + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Debug settings +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigDebug : public FDisplayClusterConfigBase +{ + bool DrawStats = false; + bool LagSimulateEnabled = false; + float LagMaxTime = 0.5f; // seconds + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; + +////////////////////////////////////////////////////////////////////////////////////////////// +// Custom development settings +////////////////////////////////////////////////////////////////////////////////////////////// +struct FDisplayClusterConfigCustom : public FDisplayClusterConfigBase +{ + TMap Args; + + virtual FString ToString() const override; + virtual bool DeserializeFromString(const FString& line) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Config/IDisplayClusterConfigManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Config/IDisplayClusterConfigManager.h new file mode 100644 index 000000000000..2a9ede57624a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Config/IDisplayClusterConfigManager.h @@ -0,0 +1,55 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterConfigTypes.h" + + +/** + * Public config manager interface + */ +struct IDisplayClusterConfigManager +{ + virtual ~IDisplayClusterConfigManager() + { } + + virtual int32 GetClusterNodesAmount() const = 0; + virtual TArray GetClusterNodes() const = 0; + virtual bool GetClusterNode(int32 idx, FDisplayClusterConfigClusterNode& cnode) const = 0; + virtual bool GetClusterNode(const FString& id, FDisplayClusterConfigClusterNode& cnode) const = 0; + virtual bool GetMasterClusterNode(FDisplayClusterConfigClusterNode& cnode) const = 0; + virtual bool GetLocalClusterNode(FDisplayClusterConfigClusterNode& cnode) const = 0; + + virtual int32 GetScreensAmount() const = 0; + virtual TArray GetScreens() const = 0; + virtual bool GetScreen(int32 idx, FDisplayClusterConfigScreen& screen) const = 0; + virtual bool GetScreen(const FString& id, FDisplayClusterConfigScreen& screen) const = 0; + virtual bool GetLocalScreen(FDisplayClusterConfigScreen& screen) const = 0; + + virtual int32 GetCamerasAmount() const = 0; + virtual TArray GetCameras() const = 0; + virtual bool GetCamera(int32 idx, FDisplayClusterConfigCamera& camera) const = 0; + virtual bool GetCamera(const FString& id, FDisplayClusterConfigCamera& camera) const = 0; + + virtual int32 GetViewportsAmount() const = 0; + virtual TArray GetViewports() const = 0; + virtual bool GetViewport(int32 idx, FDisplayClusterConfigViewport& viewport) const = 0; + virtual bool GetViewport(const FString& id, FDisplayClusterConfigViewport& viewport) const = 0; + virtual bool GetLocalViewport(FDisplayClusterConfigViewport& screen) const = 0; + + virtual int32 GetSceneNodesAmount() const = 0; + virtual TArray GetSceneNodes() const = 0; + virtual bool GetSceneNode(int32 idx, FDisplayClusterConfigSceneNode& snode) const = 0; + virtual bool GetSceneNode(const FString& id, FDisplayClusterConfigSceneNode& snode) const = 0; + + virtual int32 GetInputDevicesAmount() const = 0; + virtual TArray GetInputDevices() const = 0; + virtual bool GetInputDevice(int32 idx, FDisplayClusterConfigInput& input) const = 0; + virtual bool GetInputDevice(const FString& id, FDisplayClusterConfigInput& input) const = 0; + + virtual FDisplayClusterConfigGeneral GetConfigGeneral() const = 0; + virtual FDisplayClusterConfigStereo GetConfigStereo() const = 0; + virtual FDisplayClusterConfigRender GetConfigRender() const = 0; + virtual FDisplayClusterConfigDebug GetConfigDebug() const = 0; + virtual FDisplayClusterConfigCustom GetConfigCustom() const = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterCameraComponent.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterCameraComponent.h new file mode 100644 index 000000000000..fdd6f0ce0b47 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterCameraComponent.h @@ -0,0 +1,28 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterSceneComponent.h" +#include "DisplayClusterCameraComponent.generated.h" + + +/** + * Camera component + */ +UCLASS( ClassGroup=(Custom) ) +class DISPLAYCLUSTER_API UDisplayClusterCameraComponent + : public UDisplayClusterSceneComponent +{ + GENERATED_BODY() + +public: + UDisplayClusterCameraComponent(const FObjectInitializer& ObjectInitializer); + +public: + virtual void SetSettings(const FDisplayClusterConfigSceneNode* pConfig) override; + virtual bool ApplySettings() override; + +protected: + virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameEngine.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameEngine.h new file mode 100644 index 000000000000..5d75516787ca --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameEngine.h @@ -0,0 +1,44 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Engine/GameEngine.h" + +#include "Config/DisplayClusterConfigTypes.h" +#include "DisplayClusterOperationMode.h" + +#include "DisplayClusterGameEngine.generated.h" + + +struct IPDisplayClusterClusterManager; +struct IPDisplayClusterNodeController; +struct IPDisplayClusterInputManager; + + +/** + * Extended game engine + */ +UCLASS() +class DISPLAYCLUSTER_API UDisplayClusterGameEngine + : public UGameEngine +{ + GENERATED_BODY() + +private: + virtual void Init(class IEngineLoop* InEngineLoop) override; + virtual void PreExit() override; + virtual void Tick(float DeltaSeconds, bool bIdleMode) override; + virtual bool LoadMap(FWorldContext& WorldContext, FURL URL, class UPendingNetGame* Pending, FString& Error) override; + +protected: + virtual bool InitializeInternals(); + EDisplayClusterOperationMode DetectOperationMode(); + +private: + IPDisplayClusterClusterManager* ClusterMgr = nullptr; + IPDisplayClusterNodeController* NodeController = nullptr; + IPDisplayClusterInputManager* InputMgr = nullptr; + + FDisplayClusterConfigDebug CfgDebug; + EDisplayClusterOperationMode OperationMode = EDisplayClusterOperationMode::Disabled; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameMode.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameMode.h new file mode 100644 index 000000000000..cbe9e078126d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameMode.h @@ -0,0 +1,55 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if WITH_EDITOR +#include "Editor.h" +#endif + +#include "GameFramework/GameMode.h" +#include "DisplayClusterGameMode.generated.h" + + +struct IPDisplayCluster; + +/** + * Extended game mode + */ +UCLASS() +class DISPLAYCLUSTER_API ADisplayClusterGameMode + : public AGameMode +{ + GENERATED_BODY() + +public: + ADisplayClusterGameMode(); + virtual ~ADisplayClusterGameMode(); + +public: + UFUNCTION(BlueprintCallable, Category = "DisplayCluster") + bool IsDisplayClusterActive() const + { return bIsDisplayClusterActive; } + +protected: + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "DisplayCluster") + bool bIsDisplayClusterActive = true; + +protected: + virtual void InitGame(const FString& MapName, const FString& Options, FString& ErrorMessage) override; + virtual void StartPlay() override; + virtual void Tick(float DeltaSeconds) override; + virtual void BeginPlay() override; + virtual void BeginDestroy() override; + +protected: + bool bGameStarted = false; + +#if WITH_EDITOR +protected: + static bool bNeedSessionStart; + static bool bSessionStarted; + + FDelegateHandle EndPIEDelegate; + void OnEndPIE(const bool bSimulate); +#endif +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameModeDefault.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameModeDefault.h new file mode 100644 index 000000000000..b515c1e109bb --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterGameModeDefault.h @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterGameMode.h" +#include "DisplayClusterGameModeDefault.generated.h" + + +/** + * Extended game mode with some implemented features (navigation) + */ +UCLASS() +class DISPLAYCLUSTER_API ADisplayClusterGameModeDefault + : public ADisplayClusterGameMode +{ + GENERATED_BODY() + +public: + ADisplayClusterGameModeDefault(); + virtual ~ADisplayClusterGameModeDefault(); +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterHUD.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterHUD.h new file mode 100644 index 000000000000..aabdb547e7d8 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterHUD.h @@ -0,0 +1,27 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "GameFramework/HUD.h" +#include "DisplayClusterHUD.generated.h" + + +/** + * Extended HUD + */ +UCLASS() +class DISPLAYCLUSTER_API ADisplayClusterHUD + : public AHUD +{ + GENERATED_BODY() + +public: + ADisplayClusterHUD(const FObjectInitializer& ObjectInitializer); + +protected: + virtual void BeginPlay() override; + + /** Primary draw call for the HUD */ + virtual void DrawHUD() override; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterOperationMode.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterOperationMode.h new file mode 100644 index 000000000000..98c187cc5c38 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterOperationMode.h @@ -0,0 +1,15 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + + +/** + * Display cluster operation mode + */ +enum class EDisplayClusterOperationMode +{ + Cluster, + Standalone, + Editor, + Disabled +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPawn.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPawn.h new file mode 100644 index 000000000000..1ee5db1672b8 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPawn.h @@ -0,0 +1,74 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Pawn.h" +#include "DisplayClusterPawn.generated.h" + + +class UCameraComponent; +class USphereComponent; +class UDisplayClusterSceneComponent; +class UDisplayClusterSceneComponentSyncParent; + +struct IPDisplayClusterGameManager; + + +/** + * VR root. This pawn represents VR hierarchy in the game. + */ +UCLASS() +class DISPLAYCLUSTER_API ADisplayClusterPawn + : public APawn +{ + GENERATED_UCLASS_BODY() + +public: + inline USphereComponent* GetCollisionComponent() const + { return CollisionComponent; } + + inline UDisplayClusterSceneComponent* GetCollisionOffsetComponent() const + { return CollisionOffsetComponent; } + + inline UCameraComponent* GetCameraComponent() const + { return CameraComponent; } + +public: + /** Scene component. Specifies translation (DisplayCluster hierarchy navigation) direction. */ + UPROPERTY(EditAnywhere, Category = "DisplayCluster") + USceneComponent* TranslationDirection; + + /** Scene component. Specifies rotation center (DisplayCluster hierarchy rotation). */ + UPROPERTY(EditAnywhere, Category = "DisplayCluster") + USceneComponent* RotationAround; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // APawn + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void BeginPlay() override; + virtual void BeginDestroy() override; + virtual void Tick(float DeltaSeconds) override; + +protected: + /** Camera component */ + UPROPERTY(VisibleAnywhere, Category = "DisplayCluster") + UCameraComponent* CameraComponent; + + /** Collision component */ + UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + USphereComponent* CollisionComponent; + + /** Used as 'second' root for any childs (whole hierarchy offset) */ + UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + UDisplayClusterSceneComponent* CollisionOffsetComponent; + +private: + UDisplayClusterSceneComponentSyncParent* DisplayClusterSyncRoot; + UDisplayClusterSceneComponentSyncParent* DisplayClusterSyncCollisionOffset; + + IPDisplayClusterGameManager* GameMgr = nullptr; + + bool bIsCluster; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPawnDefault.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPawnDefault.h new file mode 100644 index 000000000000..e8a380678ae3 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPawnDefault.h @@ -0,0 +1,103 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterPawn.h" + +#include "GameFramework/FloatingPawnMovement.h" +#include "GameFramework/RotatingMovementComponent.h" + +#include "DisplayClusterPawnDefault.generated.h" + + +/** + * Extended VR root. Implements some basic features. + */ +UCLASS() +class DISPLAYCLUSTER_API ADisplayClusterPawnDefault + : public ADisplayClusterPawn +{ + GENERATED_UCLASS_BODY() + +public: + + /** Base turn rate, in deg/sec. Other scaling may affect final turn rate. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") + float BaseTurnRate; + + /** Base lookup rate, in deg/sec. Other scaling may affect final lookup rate. */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Pawn") + float BaseLookUpRate; + +public: + virtual UPawnMovementComponent* GetMovementComponent() const override + { return MovementComponent; } + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // APawn + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual void SetupPlayerInputComponent(UInputComponent* InInputComponent) override; + virtual void BeginPlay() override; + virtual void BeginDestroy() override; + virtual void Tick(float DeltaSeconds) override; + +protected: + /** + * Input callback to move forward in local space (or backward if Val is negative). + * @param Val Amount of movement in the forward direction (or backward if negative). + * @see APawn::AddMovementInput() + */ + UFUNCTION(BlueprintCallable, Category = "Pawn") + void MoveForward(float Val); + + /** + * Input callback to strafe right in local space (or left if Val is negative). + * @param Val Amount of movement in the right direction (or left if negative). + * @see APawn::AddMovementInput() + */ + UFUNCTION(BlueprintCallable, Category = "Pawn") + void MoveRight(float Val); + + /** + * Input callback to move up in world space (or down if Val is negative). + * @param Val Amount of movement in the world up direction (or down if negative). + * @see APawn::AddMovementInput() + */ + UFUNCTION(BlueprintCallable, Category = "Pawn") + void MoveUp(float Val); + + /** + * Called via input to turn at a given rate. + * @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate + */ + UFUNCTION(BlueprintCallable, Category = "Pawn") + void TurnAtRate(float Rate); + + UFUNCTION(BlueprintCallable, Category = "Pawn") + void TurnAtRate2(float Rate); + + /** + * Called via input to look up at a given rate (or down if Rate is negative). + * @param Rate This is a normalized rate, i.e. 1.0 means 100% of desired turn rate + */ + UFUNCTION(BlueprintCallable, Category = "Pawn") + void LookUpAtRate(float Rate); + +protected: + /** Movement component */ + UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + UFloatingPawnMovement* MovementComponent; + + /** Rotating movement */ + UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + URotatingMovementComponent* RotatingComponent; + + UPROPERTY(Category = Pawn, VisibleAnywhere, BlueprintReadOnly, meta = (AllowPrivateAccess = "true")) + URotatingMovementComponent * RotatingComponent2; + +private: + IPDisplayClusterGameManager* GameMgr = nullptr; + + bool bIsCluster; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPlayerController.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPlayerController.h new file mode 100644 index 000000000000..feb2c2f3b6ab --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterPlayerController.h @@ -0,0 +1,21 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "GameFramework/PlayerController.h" +#include "DisplayClusterPlayerController.generated.h" + +/** + * Extended player controller + */ +UCLASS() +class DISPLAYCLUSTER_API ADisplayClusterPlayerController + : public APlayerController +{ + GENERATED_BODY() + +protected: + virtual void BeginPlay() override; + virtual void PlayerTick(float DeltaTime) override; +}; + diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponent.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponent.h new file mode 100644 index 000000000000..84633c64e6a8 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponent.h @@ -0,0 +1,43 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Components/SceneComponent.h" +#include "Config/DisplayClusterConfigTypes.h" + +#include "DisplayClusterSceneComponent.generated.h" + + +class UDisplayClusterSceneComponentSync; + + +/** + * Extended scene component + */ +UCLASS( ClassGroup=(Custom) ) +class DISPLAYCLUSTER_API UDisplayClusterSceneComponent + : public USceneComponent +{ + GENERATED_BODY() + +public: + UDisplayClusterSceneComponent(const FObjectInitializer& ObjectInitializer); + +public: + virtual void SetSettings(const FDisplayClusterConfigSceneNode* pConfig); + virtual bool ApplySettings(); + + inline FString GetId() const + { return Config.Id; } + + inline FString GetParentId() const + { return Config.ParentId; } + +protected: + virtual void BeginPlay() override; + virtual void BeginDestroy() override; + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + +private: + FDisplayClusterConfigSceneNode Config; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSync.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSync.h new file mode 100644 index 000000000000..7e2bf4d57508 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSync.h @@ -0,0 +1,72 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Components/SceneComponent.h" +#include "Cluster/IDisplayClusterClusterSyncObject.h" +#include "DisplayClusterSceneComponentSync.generated.h" + +struct IPDisplayClusterGameManager; +struct IPDisplayClusterClusterManager; + + +/** + * Abstract synchronization component + */ +UCLASS(Abstract) +class DISPLAYCLUSTER_API UDisplayClusterSceneComponentSync + : public USceneComponent + , public IDisplayClusterClusterSyncObject +{ + GENERATED_BODY() + +public: + UDisplayClusterSceneComponentSync(const FObjectInitializer& ObjectInitializer); + + virtual ~UDisplayClusterSceneComponentSync() + { } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterClusterSyncObject + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FString GetSyncId() const override; + + virtual bool IsDirty() const override + { return true; } + + virtual void ClearDirty() override + { } + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterStringSerializable + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FString SerializeToString() const override final; + virtual bool DeserializeFromString(const FString& data) override final; + +protected: + virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + virtual void DestroyComponent(bool bPromoteChildren) override; + +protected: + virtual FTransform GetSyncTransform() const + { return FTransform(); } + + virtual void SetSyncTransform(const FTransform& t) + { } + +protected: + IPDisplayClusterGameManager* GameMgr = nullptr; + IPDisplayClusterClusterManager* ClusterMgr = nullptr; + +protected: + // Caching state + FVector LastSyncLoc; + FRotator LastSyncRot; + FVector LastSyncScale; + +private: + FString SyncId; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSyncParent.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSyncParent.h new file mode 100644 index 000000000000..d074fccbc7dd --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSyncParent.h @@ -0,0 +1,40 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterSceneComponentSync.h" +#include "DisplayClusterSceneComponentSyncParent.generated.h" + + +/** + * Synchronization component. Synchronizes parent scene component. + */ +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class DISPLAYCLUSTER_API UDisplayClusterSceneComponentSyncParent + : public UDisplayClusterSceneComponentSync +{ + GENERATED_BODY() + +public: + UDisplayClusterSceneComponentSyncParent(const FObjectInitializer& ObjectInitializer); + +protected: + virtual void BeginPlay() override; + virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override; + virtual void DestroyComponent(bool bPromoteChildren) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterClusterSyncObject + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FString GetSyncId() const override; + virtual bool IsDirty() const override; + virtual void ClearDirty() override; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // UDisplayClusterSceneComponentSync + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FTransform GetSyncTransform() const override; + virtual void SetSyncTransform(const FTransform& t) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSyncThis.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSyncThis.h new file mode 100644 index 000000000000..39f7f56325e9 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSceneComponentSyncThis.h @@ -0,0 +1,40 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterSceneComponentSync.h" +#include "DisplayClusterSceneComponentSyncThis.generated.h" + + +/** + * Synchronization component. Synchronizes himself + */ +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class DISPLAYCLUSTER_API UDisplayClusterSceneComponentSyncThis + : public UDisplayClusterSceneComponentSync +{ + GENERATED_BODY() + +public: + UDisplayClusterSceneComponentSyncThis(const FObjectInitializer& ObjectInitializer); + +protected: + virtual void BeginPlay() override; + virtual void TickComponent( float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction ) override; + virtual void DestroyComponent(bool bPromoteChildren) override; + +public: + ////////////////////////////////////////////////////////////////////////////////////////////// + // IDisplayClusterClusterSyncObject + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FString GetSyncId() const override; + virtual bool IsDirty() const override; + virtual void ClearDirty() override; + +protected: + ////////////////////////////////////////////////////////////////////////////////////////////// + // UDisplayClusterSceneComponentSync + ////////////////////////////////////////////////////////////////////////////////////////////// + virtual FTransform GetSyncTransform() const override; + virtual void SetSyncTransform(const FTransform& t) override; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterScreenComponent.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterScreenComponent.h new file mode 100644 index 000000000000..c15c9d439643 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterScreenComponent.h @@ -0,0 +1,38 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "DisplayClusterSceneComponent.h" +#include "DisplayClusterScreenComponent.generated.h" + + +/** + * Projection screen component + */ +UCLASS( ClassGroup=(Custom), meta=(BlueprintSpawnableComponent) ) +class DISPLAYCLUSTER_API UDisplayClusterScreenComponent + : public UDisplayClusterSceneComponent +{ + GENERATED_BODY() + +public: + UDisplayClusterScreenComponent(const FObjectInitializer& ObjectInitializer); + +public: + virtual void SetSettings(const FDisplayClusterConfigSceneNode* pConfig) override; + virtual bool ApplySettings() override; + + inline FVector2D GetScreenSize() const + { return Size; } + +protected: + virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + +private: + FVector2D Size; + + UPROPERTY(VisibleAnywhere, Category = Mesh) + UStaticMeshComponent* ScreenGeometryComponent = nullptr; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSettings.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSettings.h new file mode 100644 index 000000000000..fa37c4395d8f --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/DisplayClusterSettings.h @@ -0,0 +1,51 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + + +#pragma once + +#include "GameFramework/Actor.h" +#include "DisplayClusterSettings.generated.h" + + +/** + * Per-level custom settings + */ +UCLASS() +class DISPLAYCLUSTER_API ADisplayClusterSettings + : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + ADisplayClusterSettings(const FObjectInitializer& ObjectInitializer); + virtual ~ADisplayClusterSettings(); + +public: + UPROPERTY(EditAnywhere, Category = "DisplayCluster (Editor only)", meta = (DisplayName = "Config file")) + FString EditorConfigPath; + + UPROPERTY(EditAnywhere, Category = "DisplayCluster (Editor only)", meta = (DisplayName = "Node ID")) + FString EditorNodeId; + + UPROPERTY(EditAnywhere, Category = "DisplayCluster (Editor only)", meta = (DisplayName = "Show projection screens")) + bool bEditorShowProjectionScreens; + + UPROPERTY(EditAnywhere, Category = "DisplayCluster|Pawn", meta = (DisplayName = "Enable DisplayCluster collisions")) + bool bEnableCollisions; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "DisplayCluster|Pawn|Control|Movement", meta = (DisplayName = "Max speed", ClampMin = "0.0", ClampMax = "1000000.0", UIMin = "0.0", UIMax = "1000000.0")) + float MovementMaxSpeed; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "DisplayCluster|Pawn|Control|Movement", meta = (DisplayName = "Acceleration", ClampMin = "0.0", ClampMax = "1000000.0", UIMin = "0.0", UIMax = "1000000.0")) + float MovementAcceleration; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "DisplayCluster|Pawn|Control|Movement", meta = (DisplayName = "Deceleration", ClampMin = "0.0", ClampMax = "1000000.0", UIMin = "0.0", UIMax = "1000000.0")) + float MovementDeceleration; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "DisplayCluster|Pawn|Control|Movement", meta = (DisplayName = "Turning boost", ClampMin = "0.0", ClampMax = "1000000.0", UIMin = "0.0", UIMax = "1000000.0")) + float MovementTurningBoost; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "DisplayCluster|Pawn|Control|Rotation", meta = (DisplayName = "Speed", ClampMin = "0.0", ClampMax = "360.0", UIMin = "0.0", UIMax = "360.0")) + float RotationSpeed; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Game/IDisplayClusterGameManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Game/IDisplayClusterGameManager.h new file mode 100644 index 000000000000..f802928c2c97 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Game/IDisplayClusterGameManager.h @@ -0,0 +1,42 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "DisplayClusterCameraComponent.h" +#include "DisplayClusterScreenComponent.h" +#include "DisplayClusterPawn.h" + + +/** + * Public game manager interface + */ +struct IDisplayClusterGameManager +{ + virtual ~IDisplayClusterGameManager() + { } + + virtual ADisplayClusterPawn* GetRoot() const = 0; + + virtual TArray GetAllScreens() const = 0; + virtual UDisplayClusterScreenComponent* GetActiveScreen() const = 0; + virtual UDisplayClusterScreenComponent* GetScreenById(const FString& id) const = 0; + virtual int32 GetScreensAmount() const = 0; + + virtual TArray GetAllCameras() const = 0; + virtual UDisplayClusterCameraComponent* GetActiveCamera() const = 0; + virtual UDisplayClusterCameraComponent* GetCameraById(const FString& id) const = 0; + virtual int32 GetCamerasAmount() const = 0; + virtual void SetActiveCamera(int32 idx) = 0; + virtual void SetActiveCamera(const FString& id) = 0; + + virtual TArray GetAllNodes() const = 0; + virtual UDisplayClusterSceneComponent* GetNodeById(const FString& id) const = 0; + + virtual USceneComponent* GetTranslationDirectionComponent() const = 0; + virtual void SetTranslationDirectionComponent(USceneComponent* const pComp) = 0; + virtual void SetTranslationDirectionComponent(const FString& id) = 0; + + virtual USceneComponent* GetRotateAroundComponent() const = 0; + virtual void SetRotateAroundComponent(USceneComponent* const pComp) = 0; + virtual void SetRotateAroundComponent(const FString& id) = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayCluster.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayCluster.h new file mode 100644 index 000000000000..6035200a99a6 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayCluster.h @@ -0,0 +1,81 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" +#include "Modules/ModuleInterface.h" + +struct IDisplayClusterRenderManager; +struct IDisplayClusterClusterManager; +struct IDisplayClusterInputManager; +struct IDisplayClusterConfigManager; +struct IDisplayClusterGameManager; + + +/** + * Public module interface + */ +struct IDisplayCluster + : public IModuleInterface +{ + static constexpr auto ModuleName = "DisplayCluster"; + + virtual ~IDisplayCluster() = 0 + { } + + /** + * 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 IDisplayCluster& Get() + { + return FModuleManager::LoadModuleChecked(IDisplayCluster::ModuleName); + } + + /** + * 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(IDisplayCluster::ModuleName); + } + + /** + * Access to the device manager. + * + * @return Current device manager or nullptr + */ + virtual IDisplayClusterRenderManager* GetRenderMgr() const = 0; + + /** + * Access to the cluster manager. + * + * @return Current cluster manager or nullptr + */ + virtual IDisplayClusterClusterManager* GetClusterMgr() const = 0; + + /** + * Access to the input manager. + * + * @return Current cluster manager or nullptr + */ + virtual IDisplayClusterInputManager* GetInputMgr() const = 0; + + /** + * Access to the config manager. + * + * @return Current config manager or nullptr + */ + virtual IDisplayClusterConfigManager* GetConfigMgr() const = 0; + + /** + * Access to the game manager. + * + * @return Current game manager or nullptr + */ + virtual IDisplayClusterGameManager* GetGameMgr() const = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayClusterSerializable.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayClusterSerializable.h new file mode 100644 index 000000000000..bade8c7751e7 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayClusterSerializable.h @@ -0,0 +1,19 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Serialization/MemoryReader.h" +#include "Serialization/MemoryWriter.h" + + +/** + * Memory serialization interface + */ +struct IDisplayClusterSerializable +{ + virtual ~IDisplayClusterSerializable() = 0 + { } + + virtual bool Serialize (FMemoryWriter& ar) = 0; + virtual bool Deserialize(FMemoryReader& ar) = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayClusterStringSerializable.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayClusterStringSerializable.h new file mode 100644 index 000000000000..b9ee16fcce44 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/IDisplayClusterStringSerializable.h @@ -0,0 +1,17 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +/** + * String serialization interface + */ +struct IDisplayClusterStringSerializable +{ + virtual ~IDisplayClusterStringSerializable() = 0 + { } + + virtual FString SerializeToString() const = 0; + virtual bool DeserializeFromString(const FString& ar) = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Input/IDisplayClusterInputManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Input/IDisplayClusterInputManager.h new file mode 100644 index 000000000000..6ac0231c095a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Input/IDisplayClusterInputManager.h @@ -0,0 +1,42 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + + +/** + * Public input manager interface + */ +struct IDisplayClusterInputManager +{ + virtual ~IDisplayClusterInputManager() + { } + + ////////////////////////////////////////////////////////////////////////// + // Device amount + virtual uint32 GetAxisDeviceAmount() const = 0; + virtual uint32 GetButtonDeviceAmount() const = 0; + virtual uint32 GetTrackerDeviceAmount() const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Device IDs + virtual bool GetAxisDeviceIds (TArray& ids) const = 0; + virtual bool GetButtonDeviceIds (TArray& ids) const = 0; + virtual bool GetTrackerDeviceIds(TArray& ids) const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Button data access + virtual bool GetButtonState (const FString& devId, const uint8 btn, bool& curState) const = 0; + virtual bool IsButtonPressed (const FString& devId, const uint8 btn, bool& curPressed) const = 0; + virtual bool IsButtonReleased (const FString& devId, const uint8 btn, bool& curReleased) const = 0; + virtual bool WasButtonPressed (const FString& devId, const uint8 btn, bool& wasPressed) const = 0; + virtual bool WasButtonReleased (const FString& devId, const uint8 btn, bool& wasReleased) const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Axes data access + virtual bool GetAxis(const FString& devId, const uint8 axis, float& value) const = 0; + + ////////////////////////////////////////////////////////////////////////// + // Tracking data access + virtual bool GetTrackerLocation(const FString& devId, const uint8 tr, FVector& location) const = 0; + virtual bool GetTrackerQuat(const FString& devId, const uint8 tr, FQuat& rotation) const = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Render/IDisplayClusterRenderManager.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Render/IDisplayClusterRenderManager.h new file mode 100644 index 000000000000..164089875eee --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Render/IDisplayClusterRenderManager.h @@ -0,0 +1,17 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IDisplayClusterStereoDevice.h" + + +/** + * Public render manager interface + */ +struct IDisplayClusterRenderManager +{ + virtual ~IDisplayClusterRenderManager() + { } + + virtual IDisplayClusterStereoDevice* GetStereoDevice() const = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Render/IDisplayClusterStereoDevice.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Render/IDisplayClusterStereoDevice.h new file mode 100644 index 000000000000..9f77ccb0cd53 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayCluster/Public/Render/IDisplayClusterStereoDevice.h @@ -0,0 +1,128 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + + +enum class EDisplayClusterSwapSyncPolicy +{ + None = 0, // no swap sync (V-sync off) + SoftSwapSync, // software swap synchronization over network + NvSwapSync // NVIDIA hardware swap synchronization (nv_swap_lock) +}; + + +/** + * Stereo device interface + */ +struct IDisplayClusterStereoDevice +{ + virtual ~IDisplayClusterStereoDevice() + { } + + /** + * Configuration of viewport render area (whore viewport is rendered by default) + * + * @param pos - left up corner offset in viewport (pixels) + * @param size - width and height of render rectangle (pixels) + */ + virtual void SetViewportArea(const FIntPoint& pos, const FIntPoint& size) = 0; + + /** + * FOV based configuration of projection screen (standalone mode only) + * + * @param FOV - field of view + */ + virtual void SetDesktopStereoParams(float FOV) = 0; + + /** + * Custom configuration of projection screen (standalone mode only) + * + * @param screenSize - width and height of your monitor's screen (meters) + * @param screenRes - horizontal and vertical resolution of target monitor (pixels i.e. 1920, 1080) + * @param screenDist - distance between the head and monitor (meters) + */ + virtual void SetDesktopStereoParams(const FVector2D& screenSize, const FIntPoint& screenRes, float screenDist) = 0; + + /** + * Configuration of interpupillary (interocular) distance + * + * @param dist - distance between eyes (meters, i.e. 0.064). + */ + virtual void SetInterpupillaryDistance(float dist) = 0; + + /** + * Returns currently used interpupillary distance. + * + * @return - distance between eyes (meters) + */ + virtual float GetInterpupillaryDistance() const = 0; + + /** + * Configure eyes swap state + * + * @param swap - new eyes swap state. False - normal eyes left|right, true - swapped eyes right|left + */ + virtual void SetEyesSwap(bool swap) = 0; + + /** + * Returns currently used eyes swap + * + * @return - eyes swap state. False - normal eyes left|right, true - swapped eyes right|left + */ + virtual bool GetEyesSwap() const = 0; + + /** + * Toggles eyes swap state + * + * @return - new eyes swap state. False - normal eyes left|right, true - swapped eyes right|left + */ + virtual bool ToggleEyesSwap() = 0; + + /** + * Configures output flipping + * + * @param flipH - enable horizontal output flip + * @param flipV - enable vertical output flip + */ + virtual void SetOutputFlip(bool flipH, bool flipV) = 0; + + /** + * Returns current output flip settings + * + * @param flipH - (out) current horizontal output flip + * @param flipV - (out) current vertical output flip + */ + virtual void GetOutputFlip(bool& flipH, bool& flipV) const = 0; + + /** + * Set swap synchronization policy + * + * @param policy - is swap sync enabled + */ + virtual void SetSwapSyncPolicy(EDisplayClusterSwapSyncPolicy policy) = 0; + + /** + * Returns current swap synchronization policy + * + * @return - current synchronization policy + */ + virtual EDisplayClusterSwapSyncPolicy GetSwapSyncPolicy() const = 0; + + /** + * Get camera frustum culling + * + * @param NearDistance - near culling plane distance + * @param FarDistance - far culling plane distance + */ + virtual void GetCullingDistance(float& NearDistance, float& FarDistance) const = 0; + + /** + * Set camera frustum culling + * + * @param NearDistance - near culling plane distance + * @param FarDistance - far culling plane distance + */ + virtual void SetCullingDistance(float NearDistance, float FarDistance) = 0; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/DisplayClusterEditor.Build.cs b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/DisplayClusterEditor.Build.cs new file mode 100644 index 000000000000..74618605c4dc --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/DisplayClusterEditor.Build.cs @@ -0,0 +1,26 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; +using System.IO; + +public class DisplayClusterEditor : ModuleRules +{ + public DisplayClusterEditor(ReadOnlyTargetRules ROTargetRules) : base(ROTargetRules) + { + PrivateDependencyModuleNames.AddRange( new string[] { + "Core", + "CoreUObject", + "Engine", + "UnrealEd" + }); + + PrivateDependencyModuleNames.AddRange( new string[] { + "DisplayCluster" + }); + + PrivateIncludePathModuleNames.AddRange( new string[] { + "Settings", + "DisplayCluster" + }); + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditor.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditor.cpp new file mode 100644 index 000000000000..542656cbc23c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditor.cpp @@ -0,0 +1,49 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterEditor.h" +#include "DisplayClusterEditorSettings.h" + +#include "Modules/ModuleManager.h" +#include "UObject/WeakObjectPtr.h" +#include "UObject/Class.h" +#include "ISettingsModule.h" + + +#define LOCTEXT_NAMESPACE "DisplayClusterEditor" + +void FDisplayClusterEditorModule::StartupModule() +{ + RegisterSettings(); +} + +void FDisplayClusterEditorModule::ShutdownModule() +{ + UnregisterSettings(); +} + + +void FDisplayClusterEditorModule::RegisterSettings() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->RegisterSettings( + "Project", "Plugins", "nDisplay", + LOCTEXT("RuntimeSettingsName", "nDisplay"), + LOCTEXT("RuntimeSettingsDescription", "Configure nDisplay"), + GetMutableDefault() + ); + } +} + +void FDisplayClusterEditorModule::UnregisterSettings() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr("Settings")) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "nDisplay"); + } +} + + +IMPLEMENT_MODULE(FDisplayClusterEditorModule, DisplayClusterEditor); + +#undef LOCTEXT_NAMESPACE diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorEngine.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorEngine.cpp new file mode 100644 index 000000000000..9e2f8a976ad7 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorEngine.cpp @@ -0,0 +1,47 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterEditorEngine.h" +#include "DisplayClusterEditorLog.h" + +#include "DisplayCluster/Private/IPDisplayCluster.h" + + +void UDisplayClusterEditorEngine::Init(IEngineLoop* InEngineLoop) +{ + UE_LOG(LogDisplayClusterEditorEngine, VeryVerbose, TEXT("UDisplayClusterEditorEngine::Init")); + + // Initialize DisplayCluster module for editor mode + DisplayClusterModule = static_cast(&IDisplayCluster::Get()); + if (DisplayClusterModule) + { + const bool bResult = DisplayClusterModule->Init(EDisplayClusterOperationMode::Editor); + if (bResult) + { + UE_LOG(LogDisplayClusterEditorEngine, Log, TEXT("DisplayCluster module has been initialized")); + } + else + { + UE_LOG(LogDisplayClusterEditorEngine, Error, TEXT("An error occured during DisplayCluster initialization")); + } + } + else + { + UE_LOG(LogDisplayClusterEditorEngine, Error, TEXT("Couldn't initialize DisplayCluster module")); + } + + return Super::Init(InEngineLoop); +} + +void UDisplayClusterEditorEngine::PreExit() +{ + UE_LOG(LogDisplayClusterEditorEngine, VeryVerbose, TEXT("UDisplayClusterEditorEngine::PreExit")); + + Super::PreExit(); +} + +void UDisplayClusterEditorEngine::PlayInEditor(UWorld* InWorld, bool bInSimulateInEditor, FPlayInEditorOverrides Overrides) +{ + UE_LOG(LogDisplayClusterEditorEngine, VeryVerbose, TEXT("UDisplayClusterEditorEngine::PlayInEditor")); + + Super::PlayInEditor(InWorld, bInSimulateInEditor, Overrides); +} diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorEngine.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorEngine.h new file mode 100644 index 000000000000..b2dca1cd995b --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorEngine.h @@ -0,0 +1,29 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Editor/UnrealEdEngine.h" +#include "DisplayClusterEditorEngine.generated.h" + +struct IPDisplayCluster; + + +/** + * Extended editor engine + */ +UCLASS() +class UDisplayClusterEditorEngine + : public UUnrealEdEngine +{ + GENERATED_BODY() + +public: + virtual void Init(IEngineLoop* InEngineLoop) override; + virtual void PreExit() override; + virtual void PlayInEditor(UWorld* InWorld, bool bInSimulateInEditor, FPlayInEditorOverrides Overrides = FPlayInEditorOverrides()) override; + +private: + + IPDisplayCluster* DisplayClusterModule = nullptr; +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorLog.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorLog.cpp new file mode 100644 index 000000000000..63eb4c9cb589 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorLog.cpp @@ -0,0 +1,7 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterEditorLog.h" + +// Plugin-wide log categories +DEFINE_LOG_CATEGORY(LogDisplayClusterEditor); +DEFINE_LOG_CATEGORY(LogDisplayClusterEditorEngine); diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorLog.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorLog.h new file mode 100644 index 000000000000..546848939fca --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorLog.h @@ -0,0 +1,9 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" + +// Plugin-wide log categories +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterEditor, Log, All); +DECLARE_LOG_CATEGORY_EXTERN(LogDisplayClusterEditorEngine, Log, All); diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorSettings.cpp b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorSettings.cpp new file mode 100644 index 000000000000..5c1b65dc7ca3 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Private/DisplayClusterEditorSettings.cpp @@ -0,0 +1,41 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "DisplayClusterEditorSettings.h" +#include "DisplayClusterEditorEngine.h" +#include "Misc/ConfigCacheIni.h" + + +UDisplayClusterEditorSettings::UDisplayClusterEditorSettings(class FObjectInitializer const & ObjectInitializer) + : Super(ObjectInitializer) +{ + GET_MEMBER_NAME_CHECKED(UDisplayClusterEditorSettings, bEnabled); +} + +#if WITH_EDITOR +void UDisplayClusterEditorSettings::PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) +{ + if (PropertyChangedEvent.Property != nullptr) + { + FName PropertyName(PropertyChangedEvent.Property->GetFName()); + FString DefaultEnginePath = FString::Printf(TEXT("%sDefaultEngine.ini"), *FPaths::SourceConfigDir()); + + if (PropertyName == GET_MEMBER_NAME_CHECKED(UDisplayClusterEditorSettings, bEnabled)) + { + if (bEnabled) + { + GConfig->SetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), TEXT("/Script/DisplayCluster.DisplayClusterGameEngine"), DefaultEnginePath); + GConfig->SetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), TEXT("/Script/DisplayClusterEditor.DisplayClusterEditorEngine"), DefaultEnginePath); + } + else + { + GConfig->SetString(TEXT("/Script/Engine.Engine"), TEXT("GameEngine"), TEXT("/Script/Engine.GameEngine"), DefaultEnginePath); + GConfig->SetString(TEXT("/Script/Engine.Engine"), TEXT("UnrealEdEngine"), TEXT("/Script/UnrealEd.UnrealEdEngine"), DefaultEnginePath); + } + + GConfig->Flush(false, DefaultEnginePath); + } + } + + Super::PostEditChangeProperty(PropertyChangedEvent); +} +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Public/DisplayClusterEditor.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Public/DisplayClusterEditor.h new file mode 100644 index 000000000000..fc30fc50fbae --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Public/DisplayClusterEditor.h @@ -0,0 +1,22 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + + +/** + * Display Cluster editor module + */ +class FDisplayClusterEditorModule : + public IModuleInterface +{ +public: + //~ IModuleInterface interface + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + void RegisterSettings(); + void UnregisterSettings(); +}; diff --git a/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Public/DisplayClusterEditorSettings.h b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Public/DisplayClusterEditorSettings.h new file mode 100644 index 000000000000..5bf36bff6fec --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/Source/DisplayClusterEditor/Public/DisplayClusterEditorSettings.h @@ -0,0 +1,29 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "DisplayClusterEditorSettings.generated.h" + + +/** + * Implements the settings for the nDisplay + **/ +UCLASS(config = Engine, defaultconfig) +class DISPLAYCLUSTEREDITOR_API UDisplayClusterEditorSettings : public UObject +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(config, EditAnywhere, Category = Main) + bool bEnabled; + +public: + // UObject interface +#if WITH_EDITOR + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; +#endif + +private: + +}; diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/quat.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/quat.h new file mode 100644 index 000000000000..77fd64f84c03 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/quat.h @@ -0,0 +1,546 @@ + +/***************************************************************************** + * + quat.h - include file for quaternion, vector and matrix routines. + + + Overview: + + quatlib is a library of routines that implements a grab-bag of + useful routines for dealing with quaternions, vectors, and + matrices. See the quatlib man page for an overview. + + + Notes: + + - to address the quaternion elements, use the Q_X, Q_Y, Q_Z and Q_W + #defines from this file. + + - to find out which version of the library you're using, do: + + % ident /libquat.a + + (this information is in the rcsid string in quat.c) + + - see /afs/unc/proj/hmd/src/quat/{quat,vector,matrix}.c + for implementation details. + + + Conventions: + + - general-purpose quaternion routines start with q_ + + - all non-integer values are doubles by default- the exceptions + to this are old (non-open-) GL routines which use floats. + + - vector routines start with "q_vec" + + - matrix routines have the string "matrix" somewhere in their name + + - all matrices are 4x4 + + - positive rotation directions are as follows: + + about Z axis: from X axis to Y axis + about X axis: from Y axis to Z axis + about Y axis: from Z axis to X axis + + - all angles are specified in radians + + - destination parameter (if any) is always first argument (as in + Unix string routines) + + - src and dest parameters can always be the same, as long as they + are of the same type (copying is done if necessary) + + - naming conventions for conversion routines: + + q_{to,from}_whatever for routines involving quaternions + q_x_to_y for all others (ie., no "from" is used) + + + Revision History (for whole library, not just this file): + + Author Date Comments + ------ -------- ---------------------------- + Rich Holloway 09/10/01 Misc cleanup, deleted PPHIGS support, + added q_xyz_quat_xform(), renamed + qogl_matrix_mult_fixed() back to + qogl_matrix_mult(). + Mark Livingston 01/09/96 Added routines for OpenGL matrices + Rich Holloway 09/27/93 Added Gary Bishop's matrix to euler rtn + Rich Holloway 07/16/92 Added q_euler_to_col_matrix(), routines + for working with GL matrices, added + documentation for euler angle routines + Erik Erikson/ 06/26/92 Added q_xyz_quat_compose + Stefan Gottschalk/ + Russ Taylor + + Rich Holloway 05/13/92 Added Q_NULL_VECTOR, Q_ID_MATRIX + Jon Leech/ 04/29/92 Added CM_ prototypes + Erik Erikson + + Rich Holloway 09/21/90 Made into library, made all matrices 4x4, + added matrix routines for + 4x4 (standard) or 3x4 (for PPHIGS), + changed names of + routines (to avoid name conflicts with + non-library routines) by prefixing + everything with "q_". + + Russ Taylor 1990 Modified q_slerp to pick shortest path + between two angles + + Warren Robinett 12/89 Added PPHIGS support routines + + Ken Shoemake 1985 Initial version + + RCS Header: + $Id: quat.h,v 2.37 2004/07/22 20:54:42 taylorr Exp $ + * + *****************************************************************************/ + +/* prevent multiple includes */ +#ifndef Q_INCLUDED +#define Q_INCLUDED + + +/***************************************************************************** + * + #defines + * + *****************************************************************************/ + +/* for accessing the elements of q_type and q_vec_type */ +#define Q_X 0 +#define Q_Y 1 +#define Q_Z 2 +#define Q_W 3 + +/* For accessing the elements of a q_vec_type describing Euler angles */ +#define Q_YAW 0 +#define Q_PITCH 1 +#define Q_ROLL 2 + +/* tolerance for quaternion operations */ +#define Q_EPSILON (1e-10) + +/* min and max macros */ +#define Q_MAX(x, y) ( ((x) > (y)) ? (x) : (y) ) +#define Q_MIN(x, y) ( ((x) < (y)) ? (x) : (y) ) + +#define Q_ABS(x) ( ((x) > 0 ) ? (x) : (-(x)) ) + +/* + * use local definition of PI for machines that have no def in math.h; this + * value stolen from DEC Ultrix 4.1 math.h + */ +#define Q_PI 3.14159265358979323846 + +#define Q_ID_QUAT { 0.0, 0.0, 0.0, 1.0 } + +#define Q_ID_MATRIX { {1.0, 0.0, 0.0, 0.0}, \ + {0.0, 1.0, 0.0, 0.0}, \ + {0.0, 0.0, 1.0, 0.0}, \ + {0.0, 0.0, 0.0, 1.0} } + +#define Q_NULL_VECTOR { 0.0, 0.0, 0.0 } + +/* + * degree/radian conversion + */ +#define Q_DEG_TO_RAD(deg) ( ((deg)*Q_PI)/180.0 ) +#define Q_RAD_TO_DEG(rad) ( (((rad)*180.0)/Q_PI) ) + + +/***************************************************************************** + * + typedefs + * + *****************************************************************************/ + +/* basic quaternion type- scalar part is last element in array */ +typedef double q_type[4]; + +/* basic vector type */ +typedef double q_vec_type[3]; + +/* for row and column matrices */ +typedef double q_matrix_type[4][4]; + +/* for working with gl or other 4x4 float matrices */ +typedef float qgl_matrix_type[4][4]; + +/* for working with OpenGL matrices - these are really just like row matrices + ** (i.e. same bits in same order), but the decl is a 1-D array, not 2-D, sigh + */ +typedef double qogl_matrix_type[16]; + +/* special transformation type using quaternions and vectors */ +typedef struct q_xyz_quat_struct { + q_vec_type xyz; /* translation */ + q_type quat; /* rotation */ +} q_xyz_quat_type; + + + +/***************************************************************************** + ***************************************************************************** + * + function declarations + * + ***************************************************************************** + *****************************************************************************/ + +/* On some platforms, we need to specifically tell the compiler + * that these functions are to have C linkage. [why not everywhere?] + */ + +#if defined(__cplusplus) + +#ifdef FLOW +#define EXTERN_QUALIFICATION +#else +#define EXTERN_QUALIFICATION "C" +#endif /* FLOW */ + +#define BEGIN_EXTERN_BLOCK extern EXTERN_QUALIFICATION { +#define END_EXTERN_BLOCK } + +#else /* __cplusplus */ + +#define BEGIN_EXTERN_BLOCK +#define END_EXTERN_BLOCK + +#endif /* __cplusplus */ + + + +BEGIN_EXTERN_BLOCK + +/***************************************************************************** + * + strictly quaternion operations + * + *****************************************************************************/ + +/* prints a quaternion */ +void q_print (const q_type quat); + +/* make a quaternion given an axis and an angle; x,y,z is axis of + * rotation; angle is angle of rotation in radians (see also q_from_two_vecs) + * + * rotation is counter-clockwise when rotation axis vector is + * pointing at you + * + * if angle or vector are 0, the identity quaternion is returned. + */ +void q_make (q_type destQuat, + double x, double y, double z, + double angle); +void q_from_axis_angle(q_type destQuat, + double x, double y, double z, + double angle); + +/* Turn a quaternion into an axis and an angle; x,y,z is axis of + * rotation; angle is angle of rotation in radians. + * + * rotation is counter-clockwise when rotation axis vector is + * pointing at you + * + * if the identity quaternion is passed in, the angle will be + * zero and the axis will be the Z axis. + */ +void q_to_axis_angle (double *x, double *y, double *z, double *angle, + const q_type srcQuat); + +/* copy srcQuat to destQuat */ +void q_copy (q_type destQuat, const q_type srcQuat); + +/* normalizes quaternion; src and dest can be same */ +void q_normalize (q_type destQuat, const q_type srcQuat); + +/* invert quat; src and dest can be the same */ +void q_invert (q_type destQuat, const q_type srcQuat); + +/* + * computes quaternion product destQuat = qLeft * qRight. + * destQuat can be same as either qLeft or qRight or both. + */ +void q_mult (q_type destQuat, const q_type qLeft, const q_type qRight); + +/* conjugate quat; src and dest can be same */ +void q_conjugate (q_type destQuat, const q_type srcQuat); + +/* take natural log of unit quat; src and dest can be same */ +void q_log (q_type destQuat, const q_type srcQuat); + +/* exponentiate quaternion, assuming scalar part 0. src can be same as dest */ +void q_exp (q_type destQuat, const q_type srcQuat); + + +/* + * q_slerp: Spherical linear interpolation of unit quaternions. + * + * As t goes from 0 to 1, destQuat goes from startQ to endQuat. + * This routine should always return a point along the shorter + * of the two paths between the two. That is why the vector may be + * negated in the end. + * + * src == dest should be ok, although that doesn't seem to make much + * sense here. + */ +void q_slerp (q_type destQuat, const q_type startQuat, const q_type endQuat, double t); + +/***************************************************************************** + * + q_from_euler - converts 3 euler angles (in radians) to a quaternion + + Assumes roll is rotation about X, pitch + is rotation about Y, yaw is about Z. Assumes order of + yaw, pitch, roll applied as follows: + + p' = roll( pitch( yaw(p) ) ) + + See comments for q_euler_to_col_matrix for more on this. + * + *****************************************************************************/ +void q_from_euler (q_type destQuat, double yaw, double pitch, double roll); + +/* converts quat to euler angles (yaw, pitch, roll). see + * q_col_matrix_to_euler() for conventions. Note that you + * cannot use Q_X, Q_Y, and Q_Z to pull the elements out of + * the Euler as if they were rotations about these angles -- + * this will invert X and Z. You need to instead use Q_YAW + * (rotation about Z), Q_PITCH (rotation about Y) and Q_ROLL + * (rotation about X) to get them. + */ +void q_to_euler(q_vec_type yawPitchRoll, const q_type q); + +/***************************************************************************** + * + mixed quaternion operations: conversions to and from vectors & matrices + * + *****************************************************************************/ + +/* destVec = q * vec * q(inverse); vec can be same storage as destVec */ +void q_xform (q_vec_type destVec, const q_type q, const q_vec_type vec); + +/* quat/vector conversion */ +/* create a quaternion from two vectors that rotates v1 to v2 + * about an axis perpendicular to both + */ +void q_from_two_vecs (q_type destQuat, const q_vec_type v1, const q_vec_type v2); + +/* simple conversion */ +void q_from_vec (q_type destQuat, const q_vec_type srcVec); +void q_to_vec (q_vec_type destVec, const q_type srcQuat); + +/* quaternion/4x4 matrix conversions */ +void q_from_row_matrix (q_type destQuat, const q_matrix_type matrix); +void q_from_col_matrix (q_type destQuat, const q_matrix_type matrix); +void q_to_row_matrix (q_matrix_type destMatrix, const q_type srcQuat); +void q_to_col_matrix (q_matrix_type destMatrix, const q_type srcQuat); + +/* quat/ogl conversion */ +void q_from_ogl_matrix (q_type destQuat, const qogl_matrix_type matrix); +void q_to_ogl_matrix (qogl_matrix_type matrix, const q_type srcQuat); + + +/***************************************************************************** + * + strictly vector operations + * + *****************************************************************************/ + +/* prints a vector to stdout */ +void q_vec_print (const q_vec_type vec); + +/* compatibility w/ old */ +#define q_set_vec q_vec_set + +/* sets vector equal to 3 values given */ +void q_vec_set (q_vec_type vec, double x, double y, double z); + +/* copies srcVec to destVec */ +void q_vec_copy (q_vec_type destVec, const q_vec_type srcVec); + +/* adds two vectors */ +void q_vec_add (q_vec_type destVec, const q_vec_type aVec, const q_vec_type bVec); + +/* destVec = v1 - v2 (v1, v2, destVec need not be distinct storage) */ +void q_vec_subtract (q_vec_type destVec, const q_vec_type v1, const q_vec_type v2); + +/* returns value of dot product of v1 and v2 */ +double q_vec_dot_product (const q_vec_type v1, const q_vec_type v2); + +/* scale a vector (src and dest need not be distinct) */ +void q_vec_scale (q_vec_type destVec, double scaleFactor, const q_vec_type srcVec); + + +/* negate a vector to point in the opposite direction */ +void q_vec_invert (q_vec_type destVec, const q_vec_type srcVec); + +/* normalize a vector (destVec and srcVec may be the same) */ +void q_vec_normalize (q_vec_type destVec, const q_vec_type srcVec); + +/* returns magnitude of vector */ +double q_vec_magnitude (const q_vec_type vec); + +/* returns distance between two points/vectors */ +double q_vec_distance (const q_vec_type vec1, const q_vec_type vec2); + +/* computes cross product of two vectors: destVec = aVec X bVec + * destVec same as aVec or bVec ok */ +void q_vec_cross_product (q_vec_type destVec, + const q_vec_type aVec, const q_vec_type bVec); + + +/***************************************************************************** + * + strictly matrix operations + * + *****************************************************************************/ + +/* q_matrix_copy - copies srcMatrix to destMatrix (both matrices are 4x4) */ +void q_matrix_copy (q_matrix_type destMatrix, const q_matrix_type srcMatrix); + +void qogl_matrix_copy (qogl_matrix_type dest, const qogl_matrix_type src); + +/* does a 4x4 matrix multiply (the input matrices are 4x4) and + * puts the result in a 4x4 matrix. src == dest ok. + */ +void q_matrix_mult (q_matrix_type resultMatrix, + const q_matrix_type leftMatrix, + const q_matrix_type rightMatrix); + +// for backward compatibility +#define qogl_matrix_mult_fixed qogl_matrix_mult + +/* + * Computes result=left*right + * Used to be called qogl_matrix_mult_fixed because the old version + * did not compute the correct result. + */ +void qogl_matrix_mult (qogl_matrix_type result, + const qogl_matrix_type left, + const qogl_matrix_type right); + + +/***************************************************************************** + * + q_euler_to_col_matrix - euler angles should be in radians + computed assuming the order of rotation is: yaw, pitch, roll. + + This means the following: + + p' = roll( pitch( yaw(p) ) ) + + or + + p' = Mr * Mp * My * p + + Yaw is rotation about Z axis, pitch is rotation about Y axis, and roll + is rotation about X axis. In terms of these axes, then, the process is: + + p' = Mx * My * Mz * p + + where Mx = the standard Foley and van Dam column matrix for rotation + about the X axis, and similarly for Y and Z. + + Thus the calling sequence in terms of X, Y, Z is: + + q_euler_to_col_matrix(destMatrix, zRot, yRot, xRot); + * + *****************************************************************************/ +void q_euler_to_col_matrix (q_matrix_type destMatrix, + double yaw, double pitch, double roll); + +/***************************************************************************** + * + q_col_matrix_to_euler- convert a column matrix to euler angles + + input: + - vector to hold euler angles + - src column matrix + + output: + - euler angles in radians in the range -pi to pi; + vec[0] = yaw, vec[1] = pitch, vec[2] = roll + yaw is rotation about Z axis, pitch is about Y, roll -> X rot. + + notes: + - written by Gary Bishop + - you cannot use Q_X, Q_Y, and Q_Z to pull the elements out of + the Euler as if they were rotations about these angles -- + this will invert X and Z. You need to instead use Q_YAW + (rotation about Z), Q_PITCH (rotation about Y) and Q_ROLL + (rotation about X) to get them. + * + *****************************************************************************/ +void q_col_matrix_to_euler (q_vec_type yawpitchroll, const q_matrix_type colMatrix); + +/* prints 4x4 matrix */ +void q_print_matrix (const q_matrix_type matrix); + +void qogl_print_matrix (const qogl_matrix_type); + + +/***************************************************************************** + * + xyz_quat routines + * + *****************************************************************************/ + +/* invert a vector/quaternion transformation pair */ +void q_xyz_quat_invert (q_xyz_quat_type *destPtr, const q_xyz_quat_type *srcPtr); + + +/* converts a row matrix to an xyz_quat */ +void q_row_matrix_to_xyz_quat (q_xyz_quat_type * xyzQuatPtr, + const q_matrix_type rowMatrix); + +/* convert an xyz_quat to a row matrix */ +void q_xyz_quat_to_row_matrix (q_matrix_type rowMatrix, + const q_xyz_quat_type * xyzQuatPtr); + +void q_ogl_matrix_to_xyz_quat (q_xyz_quat_type * xyzQuatPtr, + const qogl_matrix_type matrix); + +void q_xyz_quat_to_ogl_matrix (qogl_matrix_type matrix, + const q_xyz_quat_type * xyzQuatPtr); + +/* compose q_xyz_quat_vecs to form a third. */ +/* C_from_A_ptr may be = to either C_from_B_ptr or B_from_A_ptr (or both) */ +void q_xyz_quat_compose (q_xyz_quat_type * C_from_A_ptr, + const q_xyz_quat_type * C_from_B_ptr, + const q_xyz_quat_type * B_from_A_ptr); + +void q_xyz_quat_xform(q_vec_type dest, const q_xyz_quat_type *xf, const q_vec_type src); + +/***************************************************************************** + * + GL support + * + *****************************************************************************/ + +/* convert from quat to GL 4x4 float row matrix */ +void qgl_to_matrix (qgl_matrix_type destMatrix, const q_type srcQuat); + + +/* qgl_from_matrix- Convert GL 4x4 row-major rotation matrix to + * unit quaternion. + * - same as q_from_row_matrix, except basic type is float, not double + */ +void qgl_from_matrix (q_type destQuat, const qgl_matrix_type srcMatrix); + +/* print gl-style matrix */ +void qgl_print_matrix (const qgl_matrix_type matrix); + +END_EXTERN_BLOCK + +#undef BEGIN_EXTERN_BLOCK +#undef END_EXTERN_BLOCK +#undef EXTERN_QUALIFICATION + +#endif /* Q_INCLUDED */ diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Analog.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Analog.h new file mode 100644 index 000000000000..1d0f7a286ba2 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Analog.h @@ -0,0 +1,210 @@ +#ifndef VRPN_ANALOG_H +#define VRPN_ANALOG_H + +#include // for NULL + +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Connection.h" // for vrpn_CONNECTION_LOW_LATENCY, etc +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_int32, vrpn_float64, etc + +#ifndef VRPN_CLIENT_ONLY +#include "vrpn_Serial.h" // for ::vrpn_SER_PARITY_NONE, etc +#endif + +#define vrpn_CHANNEL_MAX 128 + +// analog status flags +const int vrpn_ANALOG_SYNCING = (2); +const int vrpn_ANALOG_REPORT_READY = (1); +const int vrpn_ANALOG_PARTIAL = (0); +const int vrpn_ANALOG_RESETTING = (-1); +const int vrpn_ANALOG_FAIL = (-2); + +// Analog time value meaning "go find out what time it is right now" +const struct timeval vrpn_ANALOG_NOW = {0, 0}; + +class VRPN_API vrpn_Analog : public vrpn_BaseClass { +public: + vrpn_Analog(const char *name, vrpn_Connection *c = NULL); + + // Print the status of the analog device + void print(void); + + vrpn_int32 getNumChannels(void) const; + +protected: + vrpn_float64 channel[vrpn_CHANNEL_MAX]; + vrpn_float64 last[vrpn_CHANNEL_MAX]; + vrpn_int32 num_channel; + struct timeval timestamp; + vrpn_int32 channel_m_id; //< channel message id (message from server) + int status; + + virtual int register_types(void); + + //------------------------------------------------------------------ + // Routines used to send data from the server + virtual vrpn_int32 encode_to(char *buf); + /// Send a report only if something has changed (for servers) + /// Optionally, tell what time to stamp the value with + virtual void + report_changes(vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY, + const struct timeval time = vrpn_ANALOG_NOW); + /// Send a report whether something has changed or not (for servers) + /// Optionally, tell what time to stamp the value with + virtual void + report(vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY, + const struct timeval time = vrpn_ANALOG_NOW); +}; + +#ifndef VRPN_CLIENT_ONLY +class VRPN_API vrpn_Serial_Analog : public vrpn_Analog { +public: + vrpn_Serial_Analog(const char *name, vrpn_Connection *connection, + const char *port, int baud = 9600, int bits = 8, + vrpn_SER_PARITY parity = vrpn_SER_PARITY_NONE, + bool rts_flow = false); + ~vrpn_Serial_Analog(); + +protected: + int serial_fd; + char portname[1024]; + int baudrate; + unsigned char buffer[1024]; + int bufcounter; + + int read_available_characters(char *buffer, int bytes); +}; +#endif + +// vrpn_Analog_Server +// Tom Hudson, March 1999 +// +// A *Sample* Analog server. Use this or derive your own from vrpn_Analog with +// this as a guide. +// +// Write whatever values you want into channels(), then call report() +// or report_changes(). (Original spec only called for report_changes(), +// but vrpn_Analog's assumption that "no new data = same data" doesn't +// match the BLT stripchart assumption of "no intervening data = ramp". +// +// For a sample application, see server_src/sample_analog.C + +class VRPN_API vrpn_Analog_Server : public vrpn_Analog { + +public: + vrpn_Analog_Server(const char *name, vrpn_Connection *c, + vrpn_int32 numChannels = vrpn_CHANNEL_MAX); + + /// Makes public the protected base class function + virtual void + report_changes(vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY, + const struct timeval time = vrpn_ANALOG_NOW); + + /// Makes public the protected base class function + virtual void + report(vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY, + const struct timeval time = vrpn_ANALOG_NOW); + + /// For this server, the user must normally call report() or + /// report_changes() directly. This mainloop() only takes + /// care of the things any server object should do. + virtual void mainloop() { server_mainloop(); }; + + /// Exposes an array of values for the user to write into. + vrpn_float64 *channels(void) { return channel; } + + /// Sets the size of the array; returns the size actually set. + /// (May be clamped to vrpn_CHANNEL_MAX) + /// This should be used before mainloop is ever called. + vrpn_int32 setNumChannels(vrpn_int32 sizeRequested); +}; + +/// Analog server that can scale and clip its range to -1..1. +// This is useful for joysticks, to allow them to be centered and +// scaled to cover the whole range. Rather than writing directly +// into the channels array, call the setChannel() method. + +class VRPN_API vrpn_Clipping_Analog_Server : public vrpn_Analog_Server { +public: + vrpn_Clipping_Analog_Server(const char *name, vrpn_Connection *c, + vrpn_int32 numChannels = vrpn_CHANNEL_MAX); + + /// Set the clipping values for the specified channel. + /// min maps to -1, values between lowzero and highzero map to 0, + /// max maps to 1. Values less than min map to -1, values larger + /// than max map to 1. Default for each channel is -1,0,0,1 + /// It is possible to compress the range to [0..1] by setting the + /// minimum equal to the lowzero. + /// Returns 0 on success, -1 on failure. + int setClipValues(int channel, double min, double lowzero, double highzero, + double max); + + /// This method should be used to set the value of a channel. + /// It will be scaled and clipped as described in setClipValues. + /// It returns 0 on success and -1 on failure. + int setChannelValue(int channel, double value); + +protected: + typedef struct { + double minimum_val; // Value mapped to -1 + double lower_zero; // Minimum value mapped to 0 + double upper_zero; // Maximum value mapped to 0 + double maximum_val; // Value mapped to 1 + } clipvals_struct; + + clipvals_struct clipvals[vrpn_CHANNEL_MAX]; +}; + +//---------------------------------------------------------- +//************** Users deal with the following ************* + +// User routine to handle a change in analog values. This is called when +// the analog callback is called (when a message from its counterpart +// across the connection arrives). + +typedef struct _vrpn_ANALOGCB { + struct timeval msg_time; // Timestamp of analog data + vrpn_int32 num_channel; // how many channels + vrpn_float64 channel[vrpn_CHANNEL_MAX]; // analog values +} vrpn_ANALOGCB; + +typedef void(VRPN_CALLBACK *vrpn_ANALOGCHANGEHANDLER)(void *userdata, + const vrpn_ANALOGCB info); + +// Open an analog device that is on the other end of a connection +// and handle updates from it. This is the type of analog device +// that user code will deal with. + +class VRPN_API vrpn_Analog_Remote : public vrpn_Analog { +public: + // The name of the analog device to connect to + // Optional argument to be used when the Remote should listen on + // a connection that is already open. + vrpn_Analog_Remote(const char *name, vrpn_Connection *c = NULL); + + // This routine calls the mainloop of the connection it's on + virtual void mainloop(); + + // (un)Register a callback handler to handle analog value change + virtual int register_change_handler(void *userdata, + vrpn_ANALOGCHANGEHANDLER handler) + { + return d_callback_list.register_handler(userdata, handler); + }; + virtual int unregister_change_handler(void *userdata, + vrpn_ANALOGCHANGEHANDLER handler) + { + return d_callback_list.unregister_handler(userdata, handler); + } + +protected: + vrpn_Callback_List d_callback_list; + + static int VRPN_CALLBACK + handle_change_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Analog_Output.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Analog_Output.h new file mode 100644 index 000000000000..7da443cfca05 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Analog_Output.h @@ -0,0 +1,193 @@ +// vrpn_Analog_Output.h +// David Borland, September 2002 +// +// These classes are for setting values for an analog output device. The +// vrpn_Analog was getting overloaded by trying to have functionality for both +// reading and writing in it. If wanting to read analog values from a device, a +// vrpn_Analog should be used, if wanting to write analog values to a device, a +// vrpn_Analog_Output should be used. This is similar to the Tracker/Poser +// dichotomy. + +#ifndef VRPN_ANALOG_OUTPUT_H +#define VRPN_ANALOG_OUTPUT_H + +#include // for NULL + +#include "vrpn_Analog.h" // for vrpn_CHANNEL_MAX +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +#include "vrpn_Connection.h" // for vrpn_CONNECTION_RELIABLE, etc +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_int32, vrpn_float64, etc + +// Similar to vrpn_Analog, but messages are different +// Members beginning with o_ are also found in vrpn_Analog, the o_ is +// so that you can derive a class from both without getting ambiguities +class VRPN_API vrpn_Analog_Output : public vrpn_BaseClass { +public: + vrpn_Analog_Output(const char* name, vrpn_Connection* c = NULL); + + // Print the status of the analog output device + void o_print(void); + + vrpn_int32 getNumChannels() const { return o_num_channel; } + +protected: + vrpn_float64 o_channel[vrpn_CHANNEL_MAX]; + vrpn_int32 o_num_channel; + struct timeval o_timestamp; + vrpn_int32 request_m_id; //< Request to change message from client + vrpn_int32 request_channels_m_id; //< Request to change channels message + // from client + vrpn_int32 report_num_channels_m_id; //< Report of the number of active + // channels, from the server + vrpn_int32 got_connection_m_id; //< new-connection notification + int o_status; + + virtual int register_types(void); +}; + +// A *Sample* Analog output server. Use this, or derive your own server +// from vrpn_Analog_Output with this as a guide. You can remove the +// user-level callback code (both the type before this class and the +// list and the handler register/deregister) if the server is controlling +// a device directly. + +class VRPN_API vrpn_Analog_Output_Server : public vrpn_Analog_Output { +public: + vrpn_Analog_Output_Server(const char* name, vrpn_Connection* c, + vrpn_int32 numChannels = vrpn_CHANNEL_MAX); + virtual ~vrpn_Analog_Output_Server(void); + + virtual void mainloop() { server_mainloop(); } + + /// Sets the size of the array; returns the size actually set. + /// (May be clamped to vrpn_CHANNEL_MAX) + /// This should be used before mainloop is ever called. + vrpn_int32 setNumChannels(vrpn_int32 sizeRequested); + + /// Exposes an array of values for the user to read from. + const vrpn_float64* o_channels(void) const { return o_channel; }; + +protected: + virtual bool report_num_channels( + vrpn_uint32 class_of_service = vrpn_CONNECTION_RELIABLE); + virtual vrpn_int32 encode_num_channels_to(char* buf, vrpn_int32 num); + + /// Responds to a request to change one of the values by + /// setting the channel to that value. Derived class must + /// either install handlers for this routine or else make + /// its own routines to handle the request message. + static int VRPN_CALLBACK + handle_request_message(void* userdata, vrpn_HANDLERPARAM p); + + /// Responds to a request to change a number of channels + /// Derived class must either install handlers for this + /// routine or else make its own routines to handle the + /// multi-channel request message. + static int VRPN_CALLBACK + handle_request_channels_message(void* userdata, vrpn_HANDLERPARAM p); + + /// Used to notify us when a new connection is requested, so that + /// we can let the client know how many channels are active + static int VRPN_CALLBACK + handle_got_connection(void* userdata, vrpn_HANDLERPARAM p); +}; + +// A more complicated analog server that provides a +// user routine to handle a change in analog values. This is called when +// the analog callback is called (when a message from its counterpart +// across the connection arrives). This callback is called whenever +// EITHER type of change message arrives (either a single-channel change +// or a multiple-channel change. + +typedef struct _vrpn_ANALOGOUTPUTCB { + struct timeval msg_time; // Timestamp of analog data + vrpn_int32 num_channel; // how many channels + const vrpn_float64* channel; // analog values (pointer to channels) +} vrpn_ANALOGOUTPUTCB; + +typedef void(VRPN_CALLBACK* vrpn_ANALOGOUTPUTCHANGEHANDLER)( + void* userdata, const vrpn_ANALOGOUTPUTCB info); + +class VRPN_API vrpn_Analog_Output_Callback_Server + : public vrpn_Analog_Output_Server { +public: + vrpn_Analog_Output_Callback_Server( + const char* name, vrpn_Connection* c, + vrpn_int32 numChannels = vrpn_CHANNEL_MAX); + virtual ~vrpn_Analog_Output_Callback_Server(void); + + // (un)Register a callback handler to handle analog value change. + // These will be called whenever EITHER type of change message is + // received, either a single channel or multiple channels. This is + // useful for applications that "have a" server, rather than derive + // from the server. + virtual int register_change_handler(void* userdata, + vrpn_ANALOGOUTPUTCHANGEHANDLER handler) + { + return d_callback_list.register_handler(userdata, handler); + }; + virtual int + unregister_change_handler(void* userdata, + vrpn_ANALOGOUTPUTCHANGEHANDLER handler) + { + return d_callback_list.unregister_handler(userdata, handler); + } + +protected: + /// Handles BOTH types of changes messages, and will be called + /// after the vrpn_Analog_Output_Server class has already filled + /// in the values. It just calls the user callbacks with the + /// appropriate pointer to the data values. + static int VRPN_CALLBACK + handle_change_message(void* userdata, vrpn_HANDLERPARAM p); + + /// List of user-level routines that need to be called back to let + /// them know that the values have changed. + vrpn_Callback_List d_callback_list; +}; + +// Open an analog output device that is on the other end of a connection +// and send updates to it. This is the type of analog output device +// that user code will deal with. +class VRPN_API vrpn_Analog_Output_Remote : public vrpn_Analog_Output { +public: + // The name of the analog device to connect to + // Optional argument to be used when the Remote should listen on + // a connection that is already open. + vrpn_Analog_Output_Remote(const char* name, vrpn_Connection* c = NULL); + virtual ~vrpn_Analog_Output_Remote(void); + + // This routine calls the mainloop of the connection it's on + virtual void mainloop(); + + // Request the analog to change its value to the one specified. + // Returns false on failure. + virtual bool request_change_channel_value( + unsigned int chan, vrpn_float64 val, + vrpn_uint32 class_of_service = vrpn_CONNECTION_RELIABLE); + + // Request the analog to change values all at once. If more values are + // given + // than we have channels, the extra values are discarded. If less values + // are + // given than we have channels, the extra channels are set to 0. + // Returns false on failure + virtual bool request_change_channels( + int num, vrpn_float64* vals, + vrpn_uint32 class_of_service = vrpn_CONNECTION_RELIABLE); + +protected: + // How we hear about the number of active channels + static int VRPN_CALLBACK + handle_report_num_channels(void* userdata, vrpn_HANDLERPARAM p); + + // Routines used to send requests from the client + virtual vrpn_int32 encode_change_to(char* buf, vrpn_int32 chan, + vrpn_float64 val); + virtual vrpn_int32 encode_change_channels_to(char* buf, vrpn_int32 num, + vrpn_float64* vals); +}; + +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Assert.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Assert.h new file mode 100644 index 000000000000..aba934364cce --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Assert.h @@ -0,0 +1,203 @@ +/** @file + @brief Header for assert macros. + + Include guards intentionally omitted, to allow re-inclusion with different + options. + + Assertions can either do nothing, call an assert handler on failure that + prints details to stderr, or call your compiler system's assert. + + - Define `VRPN_DISABLE_ASSERTS` before including this file to forcibly + disable all asserts. + - By default, debug builds will use the standard assert method, and release + builds will do nothing. + - To unconditionally (debug and release) enable the custom assert handler, + define `VRPN_ENABLE_ASSERT_HANDLER` + - To enable the custom assert handler for debug builds only (leaving asserts + as no-ops in release builds), define `VRPN_ENABLE_ASSERT_DEBUG_HANDLER` + + + @date 2015 + + @author + Ryan Pavlik (incorporating some code modified from Boost) + Sensics, Inc. + +*/ + +// Copyright 2015 Sensics, Inc. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) +// +// Includes code adapted from the following Boost Software License v1.0 sources: +// - +// - + +// Undefine macro for safe multiple inclusion +#undef VRPN_CURRENT_FUNCTION + +// ---------------------------------------------------------- // +// Begin code adapted from +// at revision 5d353ad2b of the boost.assert repository +// https://github.com/boostorg/assert/blob/5d353ad2b92208c6ca300f4b47fdf04c87a8a593/include/boost/current_function.hpp +// +// Original notice follows: +// +// Copyright (c) 2002 Peter Dimov and Multi Media Ltd. +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// http://www.boost.org/libs/assert/current_function.html +// +#if defined(__GNUC__) || (defined(__MWERKS__) && (__MWERKS__ >= 0x3000)) || \ + (defined(__ICC) && (__ICC >= 600)) || defined(__ghs__) + +#define VRPN_CURRENT_FUNCTION __PRETTY_FUNCTION__ + +#elif defined(__DMC__) && (__DMC__ >= 0x810) + +#define VRPN_CURRENT_FUNCTION __PRETTY_FUNCTION__ + +#elif defined(__FUNCSIG__) + +#define VRPN_CURRENT_FUNCTION __FUNCSIG__ + +#elif(defined(__INTEL_COMPILER) && (__INTEL_COMPILER >= 600)) || \ + (defined(__IBMCPP__) && (__IBMCPP__ >= 500)) + +#define VRPN_CURRENT_FUNCTION __FUNCTION__ + +#elif defined(__BORLANDC__) && (__BORLANDC__ >= 0x550) + +#define VRPN_CURRENT_FUNCTION __FUNC__ + +#elif defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901) + +#define VRPN_CURRENT_FUNCTION __func__ + +#elif defined(__cplusplus) && (__cplusplus >= 201103) + +#define VRPN_CURRENT_FUNCTION __func__ + +#else + +#define VRPN_CURRENT_FUNCTION "(unknown)" + +#endif + +// End code adapted from +// ---------------------------------------------------------- // + +// ---------------------------------------------------------- // +// Begin code adapted from +// at revision 5d353ad2b of the boost.assert repository +// https://github.com/boostorg/assert/blob/5d353ad2b92208c6ca300f4b47fdf04c87a8a593/include/boost/assert.hpp +// +// Original notice follows: +// +// Copyright (c) 2001, 2002 Peter Dimov and Multi Media Ltd. +// Copyright (c) 2007, 2014 Peter Dimov +// Copyright (c) Beman Dawes 2011 +// +// Distributed under the Boost Software License, Version 1.0. +// See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt +// +// Note: There are no include guards. This is intentional. +// +// See http://www.boost.org/libs/assert/assert.html for documentation. +// + +// +// VRPN_ASSERT, VRPN_ASSERT_MSG +// + +#undef VRPN_ASSERT +#undef VRPN_ASSERT_MSG + +#if defined(VRPN_DISABLE_ASSERTS) || ( defined(VRPN_ENABLE_ASSERT_DEBUG_HANDLER) && defined(NDEBUG) ) + +#define VRPN_ASSERT(expr) ((void)0) +#define VRPN_ASSERT_MSG(expr, msg) ((void)0) + +#elif defined(VRPN_ENABLE_ASSERT_HANDLER) || ( defined(VRPN_ENABLE_ASSERT_DEBUG_HANDLER) && !defined(NDEBUG) ) + +/// @todo implementation of VRPN_LIKELY +#ifndef VRPN_LIKELY +#define VRPN_LIKELY(X) (X) +#endif + +#ifndef VRPN_API +#include "vrpn_Configure.h" +#endif + +namespace vrpn { + VRPN_API void assertion_failed(char const *expr, char const *function, + char const *file, long line); + VRPN_API void assertion_failed_msg(char const *expr, char const *msg, + char const *function, char const *file, + long line); +} // namespace vrpn + +#define VRPN_ASSERT(expr) (VRPN_LIKELY(!!(expr))? ((void)0): ::vrpn::assertion_failed(#expr, VRPN_CURRENT_FUNCTION, __FILE__, __LINE__)) +#define VRPN_ASSERT_MSG(expr, msg) (VRPN_LIKELY(!!(expr))? ((void)0): ::vrpn::assertion_failed_msg(#expr, msg, VRPN_CURRENT_FUNCTION, __FILE__, __LINE__)) + +#else + +#include // .h to support old libraries w/o - effect is the same + +#define VRPN_ASSERT(expr) assert(expr) +#define VRPN_ASSERT_MSG(expr, msg) assert((expr) && (msg)) + +#endif + +// +// VRPN_VERIFY, VRPN_VERIFY_MSG +// + +#undef VRPN_VERIFY +#undef VRPN_VERIFY_MSG + + +#if defined(VRPN_DISABLE_ASSERTS) || ( !defined(VRPN_ENABLE_ASSERT_HANDLER) && defined(NDEBUG) ) + +# define VRPN_VERIFY(expr) ((void)(expr)) +# define VRPN_VERIFY_MSG(expr, msg) ((void)(expr)) + +#else + +# define VRPN_VERIFY(expr) VRPN_ASSERT(expr) +# define VRPN_VERIFY_MSG(expr, msg) VRPN_ASSERT_MSG(expr,msg) + +#endif + +// End code adapted from +// -- + +// --------- +// Documentation +/** @def VRPN_CURRENT_FUNCTION + @brief Expands to the special preprocessor macro providing a useful + description of the current function, where available. +*/ +/** @def VRPN_ASSERT(expr) + @brief Asserts the truth of @p expr according to the configuration of + vrpn_Assert.h at the time of inclusion. If not asserting, does not evaluate + expression. +*/ +/** @def VRPN_ASSERT_MSG(expr, msg) + @brief Like VRPN_ASSERT(expr) but allows specification of a message to be + included in the case of a failed assertion. +*/ +/** @def VRPN_VERIFY(expr) + @brief Typically forwards to VRPN_ASSERT, but in cases where VRPN_ASSERT + would expand to nothing (not evaluating the expression), VRPN_VERIFY + evaluates the expression but discards the result. +*/ +/** @def VRPN_VERIFY_MSG(expr, msg) + @brief Like VRPN_VERIFY(expr) but allows specification of a message to be + included in the case of a failed assertion. +*/ \ No newline at end of file diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Auxiliary_Logger.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Auxiliary_Logger.h new file mode 100644 index 000000000000..e394a598c7ab --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Auxiliary_Logger.h @@ -0,0 +1,253 @@ +// This is a base class interface that has been designed for use by +// scientific data-collection applications that make use of VRPN to +// connect to microscope imagers and tracking system for nanoscale +// science research at UNC. + +// The idea of this interface is to enable a client GUI to start and +// stop logging of full-rate data on the server while receiving only +// a subset of the data during the experiment for preview; this keeps +// from overloading the network bandwidth with data and also keeps +// the client-side log files from filling up. When new log file(s) +// are requested, the old log files are closed. + +// Note that a particular implementation of the auxiliary logger server +// may need to know about a second connection (not the one it talks +// to its client over) in case that is where it is doing its logging. + +#ifndef VRPN_AUXILIARY_LOGGER_H +#define VRPN_AUXILIARY_LOGGER_H +#include // for NULL + +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +#include "vrpn_Connection.h" +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_int32 + +class VRPN_API vrpn_Auxiliary_Logger : public vrpn_BaseClass { +public: + vrpn_Auxiliary_Logger(const char *name, vrpn_Connection *c); + +protected: + // Handle registration of all message types we're going to deal with. + virtual int register_types(void); + vrpn_int32 request_logging_m_id; // ID of remote->server request message + vrpn_int32 report_logging_m_id; // ID of server->client response message + vrpn_int32 request_logging_status_m_id; // ID of remote->server + // status-request message + + // Pack a log description into the message whose type is passed + // as the parameter (this is used to pack both the request and + // report messages. + bool pack_log_message_of_type(vrpn_int32 type, + const char *local_in_logfile_name, + const char *local_out_logfile_name, + const char *remote_in_logfile_name, + const char *remote_out_logfile_name); + + // Unpack a log description from a message into the four strings that + // were passed in (this is used to unpack both the request and the + // report messages). + // NOTE: This routine will allocate space for the strings. The caller + // must delete [] this space when they are done with it to avoid + // memory leaks. + bool unpack_log_message_from_buffer(const char *buf, vrpn_int32 buflen, + char **local_in_logfile_name, + char **local_out_logfile_name, + char **remote_in_logfile_name, + char **remote_out_logfile_name); +}; + +// Virtual base server class for an auxiliiary logger. An implementation must +// implement the specified message-handling functions and must call the base- +// class constructor to set up the calling of them. + +class VRPN_API vrpn_Auxiliary_Logger_Server : public vrpn_Auxiliary_Logger { +public: + vrpn_Auxiliary_Logger_Server(const char *name, vrpn_Connection *c); + + // Required for servers. + virtual void mainloop(void) { server_mainloop(); } + +protected: + // Handle a logging-request message. The request contains four file + // names, two for local (to the Auxiliary server itself) and two for + // remote (the far side of its connection to the server). It must + // also respond to the client with a message saying what logging has + // been set up (using the send_logging_response function). Logging is + // turned off on a particular file by sending an empty-string name (""). + // The in/out local/remote are with respect to the connection that the + // logging is to occur on, which may or may not be the same one that the + // client has connected to the object on using the constructor above. + // Make sure to send a response saying what you did. + virtual void + handle_request_logging(const char *local_in_logfile_name, + const char *local_out_logfile_name, + const char *remote_in_logfile_name, + const char *remote_out_logfile_name) = 0; + + // Send a response to the client telling it what logging has been + // established. + bool send_report_logging(const char *local_in_logfile_name, + const char *local_out_logfile_name, + const char *remote_in_logfile_name, + const char *remote_out_logfile_name) + { + if (!d_connection) { + return false; + } + return pack_log_message_of_type( + report_logging_m_id, local_in_logfile_name, local_out_logfile_name, + remote_in_logfile_name, remote_out_logfile_name); + } + + // Handle dropped last connection on server object by turning off + // logging. The static method basically looks up the this + // pointer and calls the virtual method. A derived class should + // re-implement the non-static method below if it doesn't want to drop all + // logging or if it wants to do something else in addition. The static + // method basically just calls the non-static method. + virtual void handle_dropped_last_connection(void); + vrpn_int32 dropped_last_connection_m_id; // ID of message that all + // connections dropped + static int VRPN_CALLBACK + static_handle_dropped_last_connection(void *userdata, vrpn_HANDLERPARAM p); + + // Static portion of handling (unpacking) the request_logging message. It + // then calls the non-static virtual method above. + static int VRPN_CALLBACK + static_handle_request_logging(void *userdata, vrpn_HANDLERPARAM p); + + // Handle request for logging status. + virtual void handle_request_logging_status() = 0; + static int VRPN_CALLBACK + static_handle_request_logging_status(void *userdata, vrpn_HANDLERPARAM p); +}; + +// Generic server that will start auxiliary logs on the connection whose name +// is passed in (which can be the same as the name of the connection it is +// created on, but does not have to be). The "local" in and out are with +// respect to the new connection that is made; the "remote" in and out are with +// respect to the named connection. No logging is started in the constructor. + +class VRPN_API vrpn_Auxiliary_Logger_Server_Generic + : public vrpn_Auxiliary_Logger_Server { +public: + // Does not start logging, just records what to log when it is started. + vrpn_Auxiliary_Logger_Server_Generic(const char *logger_name, + const char *connection_to_log, + vrpn_Connection *c = NULL); + ~vrpn_Auxiliary_Logger_Server_Generic(); + + // Close an existing logging connection, then (if any of the file + // names are non-empty) open a new logging connection to the + // connection we are to log (even if this process already has a + // connection to it) and then send back the report that we've started + // logging if we are able. If we cannot open it, then fill in all + // blank names for the return report. + virtual void handle_request_logging(const char *local_in_logfile_name, + const char *local_out_logfile_name, + const char *remote_in_logfile_name, + const char *remote_out_logfile_name); + + virtual void handle_request_logging_status(); + + // If we have an active logging connection, mainloop it and save all of its + // pending messages in addition to handling the base-class functions. + // Then call the parent class mainloop(). + virtual void mainloop(void) + { + if (d_logging_connection) { + d_logging_connection->mainloop(); + d_logging_connection->save_log_so_far(); + } + vrpn_Auxiliary_Logger_Server::mainloop(); + } + +protected: + char *d_connection_name; // Name to connect to when logging. + vrpn_Connection *d_logging_connection; // Connection to use for logging. +}; + +//----------------------------------------------------------- +//************** Client code uses the following ************* + +// Type of a client routine to request new logging and to handle a +// report of changed logging. This callback is called when the +// logging server reports a new set of files, which should happen +// after each request is made. + +typedef struct _vrpn_AUXLOGGERCB { + struct timeval msg_time; // Timestamp of new logging + const char * + local_in_logfile_name; // Name of the incoming local log ("" if none). + const char *local_out_logfile_name; + const char *remote_in_logfile_name; + const char *remote_out_logfile_name; +} vrpn_AUXLOGGERCB; + +typedef void(VRPN_CALLBACK *vrpn_AUXLOGGERREPORTHANDLER)( + void *userdata, const vrpn_AUXLOGGERCB info); + +class VRPN_API vrpn_Auxiliary_Logger_Remote : public vrpn_Auxiliary_Logger { +public: + vrpn_Auxiliary_Logger_Remote(const char *name, vrpn_Connection *c = NULL); + + // Send a request to the server asking it to log the following. Each of + // these is with respect to the connection that the auxiliary logger server + // is handling, which may or may not be the one that it is connected to to + // receive this message; it refers to the other side of the new connection + // that the server establishes to do its logging. Passing a NULL or empty + // string ("") to any of the entries disables that log. + // WARNING: If the server is set to connect to its own connection and log + // it, then you must explicitly request a set of empty log files to stop + // it logging the last time because otherwise it never gets the message + // that it dropped the last connection and will continue logging after the + // object is destroyed. + bool send_logging_request(const char *local_in_logfile_name, + const char *local_out_logfile_name = "", + const char *remote_in_logfile_name = "", + const char *remote_out_logfile_name = "") + { + if (!d_connection) { + return false; + } + return pack_log_message_of_type( + request_logging_m_id, local_in_logfile_name, local_out_logfile_name, + remote_in_logfile_name, remote_out_logfile_name); + } + + bool send_logging_status_request() + { + if (!d_connection) { + return false; + } + return pack_log_message_of_type(request_logging_status_m_id, NULL, NULL, + NULL, NULL); + } + + // Register/unregister a callback handler for the logging response. + virtual int register_report_handler(void *userdata, + vrpn_AUXLOGGERREPORTHANDLER handler) + { + return d_callback_list.register_handler(userdata, handler); + }; + virtual int unregister_report_handler(void *userdata, + vrpn_AUXLOGGERREPORTHANDLER handler) + { + return d_callback_list.unregister_handler(userdata, handler); + } + + // This routine calls the mainloop of the connection it's on + virtual void mainloop(void); + +protected: + // Static handler for the logging report message. + // Use the base-class unpack method to convert the data into strings. + vrpn_Callback_List d_callback_list; + + static int VRPN_CALLBACK + handle_report_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_BaseClass.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_BaseClass.h new file mode 100644 index 000000000000..6de47b4e76a2 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_BaseClass.h @@ -0,0 +1,487 @@ +/** @file vrpn_BaseClass.h + + All types of client/server/peer objects in VRPN should be derived from the + vrpn_BaseClass type described here. This includes Tracker, Button, Analog, + Clock, Dial, ForceDevice, Sound, and Text; it should include any user-defined + objects as well. + + This class both implements code that will be shared by most (if not all) + objects in the system and forms a skeleton for the definition of new objects + by requiring certain virtual member functions to be defined. + + See the VRPN web pages or another simple type (such as vrpn_Analog) for an + example of how to create a new VRPN object type using this as a base class. +*/ + +#ifndef VRPN_BASECLASS +#define VRPN_BASECLASS + +#include // for NULL, fprintf, stderr, FILE + +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Connection.h" +#include "vrpn_Shared.h" // for timeval, vrpn_gettimeofday +#include "vrpn_Types.h" // for vrpn_int32, vrpn_uint32 + +/* +----------------------------------------------------------------------------- +Answer to the question: + "Why is there both a UNIQUE and NON-UNIQUE base class?", + or + "Why can't everything from vrpn_BaseClass be moved into +vrpn_BaseClassUnique?" + + The first reason is that removing vrpn_BaseClass would require the + vrpn_BaseClassUnique constructor to take a name and connection object as + parameters, which would cause some problems due to the way virtual base + classes are implemented in C++. + + Any class that inherits from a virtual base (either directly or several + generations removed) must provide an explicit call to the constructor + of the virtual base. This is done because the virtual base constructor + is invoked from the very first class in the constructor chain. + + Take for example vrpn_Tng3, which inherits vrpn_Button and vrpn_Serial_Analog + (and thus vrpn_Analog). Creating a new instance of a vrpn_Tng3 object will + call the constructors in this order: + Tng3 + BaseClassUnique (because it is a virtual base) + Button + BaseClass (coming from Button) + Serial_Analog + Analog + BaseClass (coming from Analog) + + Right now, BaseClassUnique's constructor has no parameters. So the + Tng3 constructor does not have to explicitly invoke BaseClassUnique, although + implicitly it will call BaseClassUnique's 0-parameter constructor before + doing anything else. But if BaseClass is eliminated, then BaseClassUnique's + constructor must do the work of creating the connection and copying the + service name. So BaseClassUnique's constructor must now take a couple + parameters, which means that every class (including Tng3, Button, Analog, and +Serial_Analog) would have to explicitly name the constructor for BaseClassUnique +in the code and specify parameters for connection and service-name, even though +only one such call to the BaseClassUnique's constructor would ever actually +occur at runtime (that of Tng3 since it's located at the lowest level of the +family tree; the rest of the calls would be ignored). This would mean inserting +"vrpn_BaseClassUnique(name,connection)" into the initializer section of every +constructor in *every* class under the BaseClassUnique subtree. + + The second reason we have both a unique and non-unique base class is that + the "register_types" virtual function must be called several times for + multiply-inherited devices, with a different virtual target in each case. + Presently, register_types() is called from vrpn_BaseClass::init(). + init() may be called multiple times using a different vftable entry for + register_types() each time (e.g. for the Tng3 it will refer once to + vrpn_Analog::register_types() and once to vrpn_Button::register_types()). + Both init() and the pure-virtual declaration of register_types() are found + in BaseClass. Moving init() up into BaseClassUnique instead of BaseClass + means that register_types() would have to move up as well. And if + register_types() is declared in the virtual base class, BaseClassUnique, + it can only have one virtual target. + + So it might appear that vrpn_BaseClass has no data members and would + therefore be easy to eliminate. However it actually does have a data + member: the vftable entry for "register_types". And this data member + *must* be duplicated in the case of multiply-inherited device because a + single object will need several distinct virtual targets for + "register_types". + + [Jeff Feasel 19 May 2005] +----------------------------------------------------------------------------- +*/ + +const int vrpn_MAX_BCADRS = 100; +///< Internal value for number of BaseClass addresses + +/// Since the sending of text messages has been pulled into the base class (so +/// that every object can send error/warning/info messages this way), these +/// definitions have been pulled in here as well. +typedef enum { + vrpn_TEXT_NORMAL = 0, + vrpn_TEXT_WARNING = 1, + vrpn_TEXT_ERROR = 2 +} vrpn_TEXT_SEVERITY; +const unsigned vrpn_MAX_TEXT_LEN = 1024; + +class VRPN_API vrpn_BaseClass; + +/// Class that handles text/warning/error printing for all objects in the +/// system. +// It is a system class, with one instance of it in existence. Each object in +// the system registers with this class when it is constructed. By default, +// this class prints all Warning and Error messages to stdout, prefaced by +// "vrpn Warning(0) from MUMBLE: ", where the 0 indicates the level of the +// message and Warning the severity, and MUMBLE the name of the object that sent +// the message. The user could create their own TextPrinter, and attach whatever +// objects they want to it. +// NOTE: Because there is a vrpn_System_TextPrinter that all vrpn_BaseClass +// objects talk to, and because those objects may be in multiple threads, the +// vrpn_TextPrinter class has to be thread-safe. This requires all user- +// callable methods to be thread-safe because the destructor may be called +// during a method call. + +class VRPN_API vrpn_TextPrinter { +public: + vrpn_TextPrinter(); + ~vrpn_TextPrinter(); + + /// Adds an object to the list of watched objects (multiple registration + /// of the same object will result in only one printing for each message + /// from the object). Returns 0 on success and -1 on failure. + /// YOU MUST REMOVE any objects from a vrpn_TextPrinter that you create + /// before destroying the printer if any connection objects survive, + /// otherwise they may call a callback function on the destroyed object. + int add_object(vrpn_BaseClass *o); + + /// Remove an object from the list of watched objects (multiple deletions + /// of the object will not cause any error condition; deletions of + /// unregistered objects will not cause errors). + void remove_object(vrpn_BaseClass *o); + + /// Change the level of printing for the object (sets the minimum level to + /// print). Default is Warnings and Errors of all levels. + void set_min_level_to_print(vrpn_TEXT_SEVERITY severity, + vrpn_uint32 level = 0); + + /// Change the ostream that will be used to print messages. Setting a + /// NULL ostream results in no printing. + void set_ostream_to_use(FILE *o); + +protected: + /// Mutex to ensure thread safety; + vrpn_Semaphore d_semaphore; + + /// Structure to hold the objects that are being watched. + class VRPN_API vrpn_TextPrinter_Watch_Entry { + public: + vrpn_BaseClass *obj; ///< Object being watched + vrpn_TextPrinter *me; + ///< Pointer to this, because used in a static function + vrpn_TextPrinter_Watch_Entry *next; + ///< Pointer to the next one in the list + }; + vrpn_TextPrinter_Watch_Entry *d_first_watched_object; + ///< Head of list of objects being watched + + FILE *d_ostream; ///< Output stream to use + vrpn_TEXT_SEVERITY d_severity_to_print; ///< Minimum severity to print + vrpn_uint32 d_level_to_print; ///< Minimum level to print + + /// Handles the text messages that come from the connections for + /// objects we are watching. + static int VRPN_CALLBACK + text_message_handler(void *userdata, vrpn_HANDLERPARAM p); +}; +// SWIG does not like this declaration. +#ifndef SWIG +extern VRPN_API vrpn_TextPrinter &vrpn_System_TextPrinter; +#endif + +/// INTERNAL class to hold members that there should only be one copy of +/// even when a class inherits from multiple vrpn_BaseClasses because it +/// inherits from multiple user-level classes. Note that not everything in +/// vrpnBaseClass should be here, because (for example) the registration of +/// types should be done for each parent class. +class VRPN_API vrpn_BaseClassUnique { + friend class VRPN_API vrpn_TextPrinter; + +public: + vrpn_BaseClassUnique(); + virtual ~vrpn_BaseClassUnique(); + + /// Returns a pointer to the connection this object is using + vrpn_Connection *connectionPtr() { return d_connection; }; + + bool shutup; // if True, don't print the "No response from server" messages. + + friend class SendTextMessageBoundCall; + class SendTextMessageBoundCall { + private: + vrpn_BaseClassUnique *_p; + vrpn_TEXT_SEVERITY _severity; + + public: + SendTextMessageBoundCall(vrpn_BaseClassUnique *device, + vrpn_TEXT_SEVERITY type) + : _p(device) + , _severity(type) + { + } + + SendTextMessageBoundCall(SendTextMessageBoundCall const &other) + : _p(other._p) + , _severity(other._severity) + { + } + + int operator()(const char *msg) const + { + struct timeval timestamp; + vrpn_gettimeofday(×tamp, NULL); + return _p->send_text_message(msg, timestamp, _severity); + } + }; + +protected: + vrpn_Connection *d_connection; ///< Connection that this object talks to + char *d_servicename; ///< Name of this device, not including the connection + /// part + + vrpn_int32 d_sender_id; ///< Sender ID registered with the connection + vrpn_int32 d_text_message_id; ///< ID for text messages + vrpn_int32 d_ping_message_id; ///< Ask the server if they are there + vrpn_int32 d_pong_message_id; ///< Server telling that it is there + + /// Registers a handler with the connection, and remembers to delete at + /// destruction. + // This is a wrapper for the vrpn_Connection call that registers + // message handlers. It should be used rather than the connection's + // function because this one will remember to unregister all of its handlers + // at object deletion time. + int register_autodeleted_handler(vrpn_int32 type, + vrpn_MESSAGEHANDLER handler, + void *userdata, + vrpn_int32 sender = vrpn_ANY_SENDER); + + /// Encodes the body of the text message into a buffer, preparing for + /// sending + static int encode_text_message_to_buffer(char *buf, + vrpn_TEXT_SEVERITY severity, + vrpn_uint32 level, + const char *msg); + + /// Decodes the body of the text message from a buffer from the connection + static int decode_text_message_from_buffer(char *msg, + vrpn_TEXT_SEVERITY *severity, + vrpn_uint32 *level, + const char *buf); + + /// Sends a NULL-terminated text message from the device d_sender_id + int send_text_message(const char *msg, struct timeval timestamp, + vrpn_TEXT_SEVERITY type = vrpn_TEXT_NORMAL, + vrpn_uint32 level = 0); + + /// Returns an object you can stream into to send a text message from the + /// device + /// like send_text_message(vrpn_TEXT_WARNING) << "Value of i is: " << i; + /// This use requires including vrpn_SendTextMessageStreamProxy.h + SendTextMessageBoundCall + send_text_message(vrpn_TEXT_SEVERITY type = vrpn_TEXT_NORMAL) + { + return SendTextMessageBoundCall(this, type); + } + + /// Handles functions that all servers should provide in their mainloop() + /// (ping/pong, for example) + /// Should be called by all servers in their mainloop() + void server_mainloop(void); + + /// Handles functions that all clients should provide in their mainloop() + /// (warning of no server, for example) + /// Should be called by all clients in their mainloop() + void client_mainloop(void); + +private: + struct { + vrpn_MESSAGEHANDLER handler; + vrpn_int32 sender; + vrpn_int32 type; + void *userdata; + } d_handler_autodeletion_record[vrpn_MAX_BCADRS]; + int d_num_autodeletions; + + int d_first_mainloop; ///< First time client_mainloop() or server_mainloop() + /// called? + struct timeval d_time_first_ping; ///< When was the first ping of this + /// unanswered group sent? + struct timeval + d_time_last_warned; ///< When is the last time we sent a warning? + int d_unanswered_ping; ///< Do we have an outstanding ping request? + int d_flatline; ///< Has it been 10+ seconds without a response? + + /// Used by client/server code to request/send "server is alive" (pong) + /// message + static int VRPN_CALLBACK handle_ping(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK handle_pong(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_connection_dropped(void *userdata, vrpn_HANDLERPARAM p); + void initiate_ping_cycle(void); +}; + +//--------------------------------------------------------------- +/// Class from which all user-level (and other) classes that communicate +/// with vrpn_Connections should derive. + +class VRPN_API vrpn_BaseClass : virtual public vrpn_BaseClassUnique { + +public: + /// Names the device and assigns or opens connection, + /// calls registration methods + vrpn_BaseClass(const char *name, vrpn_Connection *c = NULL); + + virtual ~vrpn_BaseClass(); + + /// Called once through each main loop iteration to handle updates. + /// Remote object mainloop() should call client_mainloop() and + /// then call d_connection->mainloop(). + /// Server object mainloop() should service the device and then + /// call server_mainloop(), but should not normally call + /// d_connection->mainloop(). + virtual void mainloop() = 0; + +protected: + /// Initialize things that the constructor can't. Returns 0 on + /// success, -1 on failure. + virtual int init(void); + + /// Register the sender for this device (by default, the name of the + /// device). Return 0 on success, -1 on fail. + virtual int register_senders(void); + + /// Register the types of messages this device sends/receives. + /// Return 0 on success, -1 on fail. + virtual int register_types(void) = 0; +}; + +//--------------------------------------------------------------- +// Within VRPN (and other libraries), it is wise to avoid using the +// Standard Template Library. This is very annoying, but required +// by the fact that some systems have incompatible versions of STL. +// This caused problems with any program that uses the GHOST library +// (which had its own STL on Windows), and I've heard tell of problems +// with other systems as well. On the other hand, nothing says that +// we can't have our OWN template types and use them. This next type +// is used to handle callback lists within objects. It is templated +// over the struct that is passed to the user callback. +// See vrpn_Button.h's usage for an example. + +// Disables a warning that the class requires DLL linkage to be +// used by clients of classes that include one: The classes themselves +// have DLL linkage, the code below asks for (but apparently does not +// get) DLL linkage, and the DLL-linked test programs work when things +// are as they are. Do not use this class outside of a derived class. +#ifdef _MSC_VER +#pragma warning(disable : 4251) +#endif +template class VRPN_API vrpn_Callback_List { +public: + typedef void(VRPN_CALLBACK *HANDLER_TYPE)(void *userdata, + const CALLBACK_STRUCT info); + + /// This class requires deep copies. + void operator=(const vrpn_Callback_List &from) + { + // Delete any existing elements in the list. + CHANGELIST_ENTRY *current, *next; + current = d_change_list; + while (current != NULL) { + next = current->next; + delete current; + current = next; + } + + // Copy all elements from the other list. XXX Side effect, this inverts + // the order + current = from.d_change_list; + while (current != NULL) { + register_handler(current->userdata, current->handler); + current = current->next; + } + } + + /// Call this to add a handler to the list. + int register_handler(void *userdata, HANDLER_TYPE handler) + { + CHANGELIST_ENTRY *new_entry; + + // Ensure that the handler is non-NULL + if (handler == NULL) { + fprintf(stderr, + "vrpn_Callback_List::register_handler(): NULL handler\n"); + return -1; + } + + // Allocate and initialize the new entry + if ((new_entry = new CHANGELIST_ENTRY) == NULL) { + fprintf(stderr, + "vrpn_Callback_List::register_handler(): Out of memory\n"); + return -1; + } + new_entry->handler = handler; + new_entry->userdata = userdata; + + // Add this handler to the chain at the beginning (don't check to see + // if it is already there, since duplication is okay). + new_entry->next = d_change_list; + d_change_list = new_entry; + + return 0; + }; + + /// Call this to remove a handler from the list (if it exists) + int unregister_handler(void *userdata, HANDLER_TYPE handler) + { + // The pointer at *snitch points to victim + CHANGELIST_ENTRY *victim, **snitch; + + // Find a handler with this registry in the list (any one will do, + // since all duplicates are the same). + snitch = &d_change_list; + victim = *snitch; + while ((victim != NULL) && ((victim->handler != handler) || + (victim->userdata != userdata))) { + snitch = &((*snitch)->next); + victim = victim->next; + } + + // Make sure we found one + if (victim == NULL) { + fprintf( + stderr, + "vrpn_Callback_List::unregister_handler: No such handler\n"); + return -1; + } + + // Remove the entry from the list + *snitch = victim->next; + delete victim; + + return 0; + }; + + /// This will pass the referenced parameter as a const to all the callbacks. + void call_handlers(const CALLBACK_STRUCT &info) + { + CHANGELIST_ENTRY *handler = d_change_list; + while (handler != NULL) { + handler->handler(handler->userdata, info); + handler = handler->next; + } + }; + + /// The list starts out empty + vrpn_Callback_List() + : d_change_list(NULL){}; + + /// Clear the list upon destruction if it is not empty already + ~vrpn_Callback_List() + { + while (d_change_list != NULL) { + CHANGELIST_ENTRY *next = d_change_list->next; + delete d_change_list; + d_change_list = next; + } + }; + +protected: + typedef struct vrpn_CBS { + void *userdata; + HANDLER_TYPE handler; + struct vrpn_CBS *next; + } CHANGELIST_ENTRY; + CHANGELIST_ENTRY *d_change_list; +}; + +// End of defined VRPN_BASECLASS for vrpn_BaseClass.h +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Button.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Button.h new file mode 100644 index 000000000000..65d1d1a5e621 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Button.h @@ -0,0 +1,296 @@ +#ifndef VRPN_BUTTON_H +#include // for NULL + +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_int32, vrpn_float64, etc + +class VRPN_API vrpn_Connection; +struct vrpn_HANDLERPARAM; + +const int vrpn_BUTTON_MAX_BUTTONS = 256; +const int VRPN_BUTTON_BUF_SIZE = 256; + +// Base class for buttons. Definition +// of remote button class for the user is at the end. + +const int vrpn_BUTTON_MOMENTARY = 10; +const int vrpn_BUTTON_TOGGLE_OFF = 20; +const int vrpn_BUTTON_TOGGLE_ON = 21; +const int vrpn_BUTTON_LIGHT_OFF = 30; +const int vrpn_BUTTON_LIGHT_ON = 31; +const int vrpn_ALL_ID = -99; + +/** This is the base class for both the client and server for a button + device (a device with one or more boolean switches). Any server + should actually derive from the vrpn_Button_Filter class, described + next, which enables toggling any of the buttons. **/ + +class VRPN_API vrpn_Button : public vrpn_BaseClass { +public: + vrpn_Button(const char *name, vrpn_Connection *c = NULL); + virtual ~vrpn_Button(void); + + // Print the status of the button + void print(void); + + virtual void set_momentary(vrpn_int32 which_button); + virtual void set_toggle(vrpn_int32 which_button, vrpn_int32 current_state); + virtual void set_all_momentary(void); + virtual void set_all_toggle(vrpn_int32 default_state); + +protected: + unsigned char buttons[vrpn_BUTTON_MAX_BUTTONS]; + unsigned char lastbuttons[vrpn_BUTTON_MAX_BUTTONS]; + vrpn_int32 minrate[vrpn_BUTTON_MAX_BUTTONS]; + vrpn_int32 num_buttons; + struct timeval timestamp; + vrpn_int32 change_message_id; // ID of change button message to connection + vrpn_int32 states_message_id; // ID of button-states message to connection + vrpn_int32 admin_message_id; // ID of admin button message to connection + + virtual int register_types(void); + virtual void report_changes(void); + virtual void report_states(void); // Calls Button or Button_Filter encode + virtual vrpn_int32 encode_to(char *buf, vrpn_int32 button, + vrpn_int32 state); + virtual vrpn_int32 encode_states_to(char *buf); +}; + +/** All button servers should derive from this class, which provides + the ability to turn any of the buttons into toggles (using messages + from the remote button object). **/ + +class VRPN_API vrpn_Button_Filter : public vrpn_Button { +public: + vrpn_int32 buttonstate[vrpn_BUTTON_MAX_BUTTONS]; + virtual void set_momentary(vrpn_int32 which_button); + virtual void set_toggle(vrpn_int32 which_button, vrpn_int32 current_state); + virtual void set_all_momentary(void); + virtual void set_all_toggle(vrpn_int32 default_state); + void set_alerts(vrpn_int32); + +protected: + int send_alerts; + vrpn_Button_Filter(const char *, vrpn_Connection *c = NULL); + vrpn_int32 + alert_message_id; // used to send back to alert button box for lights + virtual vrpn_int32 encode_states_to(char *buf); + virtual void report_changes(void); + + // This method makes sure we send a states message whenever we get a ping + // from + // a client object or a new connection. + static int VRPN_CALLBACK + handle_ping_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +#ifndef VRPN_CLIENT_ONLY + +// Button server that lets you set the values for the buttons directly and +// then have it update if needed. This class should be used by devices that +// can have several sets of buttons in them and don't want to derive from the +// Button class themselves. An example is the InterSense 900 features found in +// the Fastrak server (which may have several button devices, one for each +// sensor). + +class VRPN_API vrpn_Button_Server : public vrpn_Button_Filter { +public: + vrpn_Button_Server(const char *name, vrpn_Connection *c, + int numbuttons = 1); + + /// Tells how many buttons there are (may be clipped to MAX_BUTTONS) + int number_of_buttons(void); + + /// Called once each time through the server program's mainloop to handle + /// various functions (like setting toggles, reporting changes, etc). + virtual void mainloop(); + + /// Allows the server program to set current button states (to 0 or 1) + int set_button(int button, int new_value); +}; + +// Example button server code. This button device causes its buttons to +// be pressed and released at the interval specified (default 1/sec). It +// has the specified number of buttons (default 1). +// This class is derived from the vrpn_Button_Filter class, so that it +// can be made to toggle its buttons using messages from the client. + +class VRPN_API vrpn_Button_Example_Server : public vrpn_Button_Filter { +public: + vrpn_Button_Example_Server(const char *name, vrpn_Connection *c, + int numbuttons = 1, vrpn_float64 rate = 1.0); + + virtual void mainloop(); + +protected: + vrpn_float64 _update_rate; // How often to toggle +}; + +// Button device that is connected to a parallel port and uses the +// status bits to read from the buttons. There can be up to 5 buttons +// read this way. +class VRPN_API vrpn_Button_Parallel : public vrpn_Button_Filter { +public: + // Open a button connected to the local machine, talk to the + // outside world through the connection. + vrpn_Button_Parallel(const char *name, vrpn_Connection *connection, + int portno, unsigned porthex = 0); + ~vrpn_Button_Parallel(); + +protected: + int port; + int status; + + virtual void read(void) = 0; +#ifdef _WIN32 + int openGiveIO(void); +#endif // _WIN32 +}; + +// Open a Python (or Hiball Button) that is connected to a parallel port. +// See www.vrpn.org/UNC_python.html for a description of how to make +// a connector that uses the parallel port this way. Note that this +// use of a parallel port can result in damage to the motherboard if +// voltage spikes (static) are passed through if care is not taken. +// This interface is intended for use at UNC. No warranty is expressed +// or implied for use elsewhere (use at your own risk). +class VRPN_API vrpn_Button_Python : public vrpn_Button_Parallel { +public: + vrpn_Button_Python(const char *name, vrpn_Connection *c, int p); + vrpn_Button_Python(const char *name, vrpn_Connection *c, int p, + unsigned ph); + + virtual void mainloop(); + +protected: + virtual void read(void); + bool d_first_fail; +}; + +// Button device that is connected to the serial port. +class VRPN_API vrpn_Button_Serial : public vrpn_Button_Filter { +public: + vrpn_Button_Serial(const char *name, vrpn_Connection *c, + const char *port = "/dev/ttyS1/", long baud = 38400); + virtual ~vrpn_Button_Serial(); + +protected: + char portname[VRPN_BUTTON_BUF_SIZE]; + long baudrate; + int serial_fd; + int status; + + unsigned char + buffer[VRPN_BUTTON_BUF_SIZE]; // char read from the button so far + vrpn_uint32 bufcount; // number of char in the buffer + + virtual void read() = 0; +}; + +// Open a Fakespace Pinch Glove System that is connected to a serial port. There +// are total of 10 buttons. Buttons 0-4 are fingers for the right hand-thumb +// first and pinkie last-while buttons 5-9 are for the left hand-thumb first. +// The report you get back is the finger is touching. So you will not have a +// state where only one button is ON. +class VRPN_API vrpn_Button_PinchGlove : public vrpn_Button_Serial { +public: + vrpn_Button_PinchGlove(const char *name, vrpn_Connection *c, + const char *port = "/dev/ttyS1/", long baud = 38400); + + virtual void mainloop(); + +protected: + bool reported_failure; + virtual void read(); + void + report_no_timestamp(); // set the glove to report data without timestamp +}; + +#endif // VRPN_CLIENT_ONLY + +//---------------------------------------------------------- +//************** Users deal with the following ************* + +// User routine to handle a change in button state. This is called when +// the button callback is called (when a message from its counterpart +// across the connection arrives). The pinch glove has 5 different states of on +// since it knows which fingers are touching. This pinch glove behavior is +// non-standard and will be removed in a future version. Button states should +// be considered like booleans. +#define VRPN_BUTTON_OFF (0) +#define VRPN_BUTTON_ON (1) + +typedef struct _vrpn_BUTTONCB { + struct timeval msg_time; // Time of button press/release + vrpn_int32 button; // Which button (numbered from zero) + vrpn_int32 state; // button state (0 = off, 1 = on) +} vrpn_BUTTONCB; +typedef void(VRPN_CALLBACK *vrpn_BUTTONCHANGEHANDLER)(void *userdata, + const vrpn_BUTTONCB info); + +// This is a new button callback type that was added in VRPN 7.31. It +// tells the current state of all of the buttons on the device. It is +// called whenever a button server receives a new connection request. It +// is intended to deal with the issue of not knowing what state toggled +// buttons are in when a client connects. +typedef struct _vrpn_BUTTONSTATECB { + struct timeval msg_time; // Timestamp of analog data + vrpn_int32 num_buttons; // how many buttons + vrpn_int32 states[vrpn_BUTTON_MAX_BUTTONS]; // button state values +} vrpn_BUTTONSTATESCB; +typedef void(VRPN_CALLBACK *vrpn_BUTTONSTATESHANDLER)( + void *userdata, const vrpn_BUTTONSTATESCB info); + +// Open a button that is on the other end of a connection +// and handle updates from it. This is the type of button that user code will +// deal with. + +class VRPN_API vrpn_Button_Remote : public vrpn_Button { +public: + // The name of the button device to connect to. Optional second + // argument is used when you already have an open connection you + // want it to listen on. + vrpn_Button_Remote(const char *name, vrpn_Connection *cn = NULL); + virtual ~vrpn_Button_Remote(void); + + // This routine calls the mainloop of the connection it's on + virtual void mainloop(); + + // (un)Register a callback handler to handle a button state change + virtual int register_change_handler(void *userdata, + vrpn_BUTTONCHANGEHANDLER handler) + { + return d_callback_list.register_handler(userdata, handler); + }; + virtual int unregister_change_handler(void *userdata, + vrpn_BUTTONCHANGEHANDLER handler) + { + return d_callback_list.unregister_handler(userdata, handler); + } + + // (un)Register a callback handler to handle buttons states reports + virtual int register_states_handler(void *userdata, + vrpn_BUTTONSTATESHANDLER handler) + { + return d_states_callback_list.register_handler(userdata, handler); + }; + virtual int unregister_states_handler(void *userdata, + vrpn_BUTTONSTATESHANDLER handler) + { + return d_states_callback_list.unregister_handler(userdata, handler); + } + +protected: + vrpn_Callback_List d_callback_list; + static int VRPN_CALLBACK + handle_change_message(void *userdata, vrpn_HANDLERPARAM p); + + vrpn_Callback_List d_states_callback_list; + static int VRPN_CALLBACK + handle_states_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +#define VRPN_BUTTON_H +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Configure.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Configure.h new file mode 100644 index 000000000000..dc7960402de6 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Configure.h @@ -0,0 +1,544 @@ +#ifndef VRPN_CONFIGURE_H + +//-------------------------------------------------------------- +/* IMPORTANT NOTE: If this file is named vrpn_Configure.h, it is + AUTOMATICALLY GENERATED from vrpn_Configure.h.cmake_in + using the options selected in CMake. Do not edit this + autogenerated vrpn_Configure.h because your changes will be + overwritten. + + Until all modules are fully configured using CMake, you may + have to edit the paths that are listed near the bottom of the + first section of the input file, vrpn_Configure.h.cmake_in + then re-run CMake to regenerate vrpn_Configure.h. */ +//-------------------------------------------------------------- + +// If true, only build the client-side code into the VRPN library. +// This makes it smaller and requires less linking with external +// libraries. +// +// If this is defined in this header file, it means that only the +// client library was built, so the header file was able to show +// this specialization. If it's not defined here, that may mean both +// libraries were built, passing the definition for the client library +// directly to the compiler, meaning that defining it in your app +// is up to you. +// +// The ifndef here means that it's always safe for you to define +// VRPN_CLIENT_ONLY without risking re-definition warnings/errors. +#ifndef VRPN_CLIENT_ONLY +#define VRPN_CLIENT_ONLY +#endif + +//-------------------------------------------------------------- +/* This file contains configuration options for VRPN. The first + section has definition lines that can be commented in or out + at build time. The second session has automaticly-generated + directives and should not be edited. */ +//-------------------------------------------------------------- + +//--------------------------------------------------------// +// EDIT BELOW THIS LINE FOR NORMAL CONFIGURATION SETTING. // +//--------------------------------------------------------// + +//----------------------- +// Default port to listen on for a server. It used to be 4500 +// up through version 6.03, but then all sorts of VPNs started +// using this, as did Microsoft. Port 3883 was assigned to VRPN +// by the Internet Assigned Numbers Authority (IANA) October, 2003. +// Change this to make a location-specific default if you like. +// The parentheses are to keep it from being expanded into something +// unexpected if the code has a dot after it. +#define vrpn_DEFAULT_LISTEN_PORT_NO (3883) + +//----------------------- +// Use the std::chrono library for time, rather than gettimeofday. +/* #undef VRPN_USE_STD_CHRONO */ + +//----------------------- +// Use compile-time static asserts. +#define VRPN_USE_STATIC_ASSERTIONS + +//----------------------- +// Use Winsock2 library rather than Winsock. +#define VRPN_USE_WINSOCK2 + +//----------------------- +// Instructs VRPN to expose the vrpn_gettimeofday() function also +// as gettimeofday() so that external programs can use it. This +// is put here for Windows. This function should not really be +// implemented within VRPN, but it was expedient to include it +// when porting applications to Windows. Turn this off if you have +// another implementation, or if you want to only call +// vrpn_gettimeofday() directly. +/* #undef VRPN_EXPORT_GETTIMEOFDAY */ + +//----------------------- +// Tells VRPN to compile with support for the Message-Passing +// Interface (MPI) library. There is a configuration section below +// that has a library path for the MPI library to link against. +// You will need to add the path to mpi.h and other needed files +// into your Visual Studio Tools/Options/Projects and Solutions/ +// C++ Directories include path. The original implementation is +// done with MPICH2, but an attempt has been made to use only +// MPI version 1 basic functions. +/* #undef VRPN_USE_MPI */ + +//----------------------- +// Tells VRPN to compile with support for the Modbus +// library. +/* #undef VRPN_USE_MODBUS */ + +//----------------------- +// Instructs VRPN to use phantom library to construct a unified +// server, using phantom as a common device, and phantom +// configuration in .cfg file. +/* #undef VRPN_USE_PHANTOM_SERVER */ + +//------------------------ +// Instructs vrpn to use SensAble's HDAPI rather than GHOST library. +// Only used in conjuntion with VRPN_USE_PHANTOM_SERVER. +// PLEASE SPECIFY PATH TO HDAPI IN NEXT SECTION IF YOU USE THIS. +// Also, you need to go to the vrpn_phantom and vrpn_server projects +// and remove the GHOST include directories from the include paths. +// Yes, HDAPI fails if it even has them in the path (as so many other +// things also fail). At least we're rid of them now. When you +// uncomment it (to use GHOST), add the following to the include +// directories for the vrpn_phantom project: $(SYSTEMDRIVE)\Program +// Files\SensAble\GHOST\v4.0\include,$(SYSTEMDRIVE)\Program +// Files\SensAble\GHOST\v4.0\external\stl, +/* #undef VRPN_USE_HDAPI */ + +//------------------------ +// Instructs vrpn to use Ghost 3.1 instead of Ghost 3.4. +// Only used in conjuntion with VRPN_USE_PHANTOM_SERVER. +// PLEASE SPECIFY PATH TO GHOSTLIB IN NEXT SECTION IF YOU USE THIS +// (This is expected to be used on systems where Ghost 4.0 is not +// available, such as the SGI platform. If you are using this on +// a Windows PC with Visual Studio, you will need to alter +// server_src/vrpn_phantom.dsp to reference the Ghost 3.1 include +// paths.) +/* #undef VRPN_USE_GHOST_31 */ + +//----------------------- +// Instructs VRPN to use the high-performance timer code on +// Windows, rather than the default clock which has an infrequent +// update. At one point in the past, an implementation of this +// would only work correctly on some flavors of Windows and with +// some types of CPUs. +// There are actually two implementations +// of the faster windows clock. The original one, made by Hans +// Weber, checks the clock rate to see how fast the performance +// clock runs (it takes a second to do this when the program +// first calls vrpn_gettimeofday()). The second version by Haris +// Fretzagias relies on the timing supplied by Windows. To use +// the second version, also define VRPN_WINDOWS_CLOCK_V2. +#define VRPN_UNSAFE_WINDOWS_CLOCK +#define VRPN_WINDOWS_CLOCK_V2 + +//----------------------- +// Instructs VRPN library and server to include code that uses +// the DirectX SDK. If you set this, you may to edit the +// system configuration section below to point at the correct version +// of DirectX. WARNING: With the August 2006 DirectX SDK, you +// cannot link against the debug library in Visual Studio 6.0, +// only the release. Hopefully, Visual Studio.NET doesn't have +// this problem. +// IMPORTANT! If you define this, you need to edit the Tools/Options +// menu: +// For Visual studio 6, use the Directories tab, and add the +// include and lib paths to the TOP of the lists for all configurations. +// For Visual studio .NET, add to the top of the Projects and Solutions/ +// VC++ Directories entry. +// This will let the code find the right version when it compiles. +/* #undef VRPN_USE_DIRECTINPUT */ +/* #undef VRPN_USE_WINDOWS_XINPUT */ + +// The DirectInput-based zSight tracker requires ATL for smart pointers, +// which sadly isn't everywhere (VC Express, MXE cross compiling, ...). +#define VRPN_HAVE_ATLBASE + +//----------------------- +// Instructs VRPN library and server to include code that uses +// the DirectShow SDK. If you set this, you may to edit the +// system configuration section below to point at the correct version +// of the Platform SDK. WARNING: With the August 2006 DirectX SDK, you +// cannot link against the debug library in Visual Studio 6.0, +// only the release. Visual Studio.NET doesn't have this problem. +/* #undef VRPN_USE_DIRECTSHOW */ + +//----------------------- +// Instructs the VRPN server to create an entry for the Adrienne +// time-code generator. This is a device that produces time values +// from an analog video stream so that events in the virtual world +// can be synchronized with events on a movie. The Adrienne folder +// should be located at the same level as the VRPN folder for the +// code to find it. +/* #undef VRPN_INCLUDE_TIMECODE_SERVER */ +/* #undef VRPN_ADRIENNE_INCLUDE_FILENAME */ +/* #undef VRPN_ADRIENNE_INCLUDE_HAS_EXTERN_C */ + +//----------------------- +// Compiles the InterSense Tracker using the +// InterSense Interface Libraries SDK (tested for version +// 3.45) on windows. This should work with all Intersense trackers, +// both the USB and the serial port versions. The files isense.h, +// types.h and isense.c should be put in a directory called 'isense' +// at the same level as the vrpn directory. The isense.dll should +// be put either in Windows/system32 or in the location where the +// executable lives or somewhere on the path. +/* #undef VRPN_INCLUDE_INTERSENSE */ + +//----------------------- +// Instructs VRPN library and server to include code that uses +// the National Instruments Nidaq library to control analog outputs. +// Later in this file, we also instruct the compiler to link with +// the National Instruments libraries if this is defined. Either or +// both of these can be defined, depending on which library you +// need to use. +/* #undef VRPN_USE_NATIONAL_INSTRUMENTS */ +/* #undef VRPN_USE_NATIONAL_INSTRUMENTS_MX */ + +//----------------------- +// Instructs VRPN library and server to include code that uses +// the US Digital SEI/A2 library to control analog inputs from the +// A2 absolute encoder. +// Later in this file, we also instruct the compiler to link with +// the US Digital library if this is defined. You also need to +// define VRPN_USE_NATIONAL_INSTRUMENTS_MX above if you want to +// use this. +/* #undef VRPN_USE_USDIGITAL */ + +//----------------------- +// Instructs VRPN to use the default room space transforms for +// the Desktop Phantom as used in the nanoManipulator application +// rather than the default world-origin with identity rotation. +// Please don't anyone new use the room space transforms built +// into VRPN -- they are a hack pulled forward from Trackerlib. +#define DESKTOP_PHANTOM_DEFAULTS + +//------------------------ +// Instructs VRPN to use microscribe3D library to construct a unified +// server +/* #undef VRPN_USE_MICROSCRIBE */ + +//------------------------ +// Compiles the VRPN library with the PhaseSpace Tracker using the +// PhaseSpace OWL API on Linux and Windows. +// +// In Linux: +// The PhaseSpace header files (owl.h, etc) and libraries (libowlsock) +// should be placed in the phasespace directory at the same level as +// the vrpn folder. Also, PHASESPACE needs to be uncommented in the +// server_src/Makefile so that the libraries are properly linked. +// libowlsock.so will need to be present in the directory of the +// final executable or in the default library path such as /usr/lib +// +// In Windows: +// The PhaseSpace header files (owl.h, etc) should be placed in the +// phasespace directory at the same level as the vrpn folder. +// libowlsock.lib will need to be located there as well. +// libowlsock.dll will need to be in the path or with the executable +// at run time. Edit the path below to say where the .lib file +// can be found. +// +/* #undef VRPN_INCLUDE_PHASESPACE */ + +//----------------------- +// Instructs VRPN to use a DLL interface on Windows systems. +// When using this, link with VRPNDLL.LIB (and VRPN.DLL) rather +// than VRPN.LIB in user code. This is experimental and is +// under development to enable C# and other languages to pull in +// VRPN. This is only needed when trying to link VRPN with +// languages other than C++ (and not even for Java). If you don't +// have a good reason to, don't define it. +// Not implemented for .so-based Unix systems. +/* #undef VRPN_USE_SHARED_LIBRARY */ + +//------------------------ +// Instructs VRPN to use GPM Linux interface mouse interface. +// WARNING: If you define this, then you must also edit the server_src +// Makefile to include "-lgpm" into the SYSLIBS definition line for the +// architecture you use this on. We had to change this because not all +// Linux releases included this library. +/* #undef VRPN_USE_GPM_MOUSE */ + +//------------------------ +// Instructs VRPN to use the Motion C API library to interface VRPN to +// the their MotionNode tracker. Requires the shared library at run-time +// to function. No external dependencies to build. +/* #undef VRPN_USE_MOTIONNODE */ + +//------------------------ +// Instructs VRPN to compile code for the Nintendo Wii Remote controller, +// getting access to it through the Wiiuse library in Windows and Linux. +// Note that this requires installing a bunch of other stuff, and that some +// bluetooth stacks cause people trouble. See the README file in the WiiUse +// library for more info. Also note that the +// WiiUse library is GPL, which is more restrictive than the VRPN public- +// domain license, so check out its license file before building this driver +// into your code. The original WiiUse library was abandoned and a new +// fork by Ryan Pavlik is available at https://github.com/rpavlik/wiiuse. +// To get the WiiUse library to compile on Visual Studio 2005 (apparently +// not for VS 2008), you need to add the include path +// to the driver developer kit (C:\WINDDK\3790.1830\inc\wxp) and the +// library path to hid.lib (C:\WINDDK\3790.1830\lib\wxp\i386) to the +// include and library directories in Visual Studio. +// Also, edit the configuration below to point to the WiiUse include +// file and library. +// Note that the wiiuse.dll needs to be in the path when running a server +// that uses WiiUse in Windows. +/* #undef VRPN_USE_WIIUSE */ + +// Instructs VRPN to compile code to handle Hillcrest Labs' Freespace +// devices such as the Loop, and FRCM. You will also need the libfreespace +// library which is available at +// http://libfreespace.hillcrestlabs.com/content/download. +// There are prebuilt binaries for Windows, and source available that should +// work on Windows, Linux or OS X. You will need to make sure the header files +// and library are accessible to the compiler. libfreespace is released under +// the LGPL and we (Hillcrest Labs) view static and dynamic linking as the same. +// We (Hillcrest Labs) do not require code linked to libfreespace (statically or +// dynamically) to be released under any particular license. +/* #undef VRPN_USE_FREESPACE */ + +//------------------------ +// Instructs VRPN to include code for the Novint Falcon haptic device. +// Access is provided through the libnifalcon library library on Windows, +// MacOSX and Linux. This may require additional libraries for programming +// USB devices. Please consult the corresponding homepages. +/* #undef VRPN_USE_LIBNIFALCON */ + +//------------------------ +// (OBSOLETE) Instructs VRPN to compile code to use Trivisio's Colibri inertial +// tracker. You will also need the SDK, which is available at +// http://www.trivisio.com/products/motiontracking/colibri#download +// (tested on Windows). VRPN_TRIVISIOCOLIBRI_H and +// VRPN_TRIVISIOCOLIBRI_LIB_PATH +// below point to the default installation locations on Windows. Edit them +// if installed elsewhere. Note that Trivisio.dll and pthreadVC2.dll need to be +// in +// the path when running the server on Windows +/* #undef VRPN_USE_TRIVISIOCOLIBRI */ + +//------------------------ +// Compiles the VRPN library with the Trivisio Colibri tracker using the +// ColibriAPI on Linux and Windows. +// +// In Linux: +// The header files (colibri_api.h, etc) and library (colibri-api) +// should be placed in the /vrpn/trivisio directory. +// libcolibri-api.so will need to be present in the default library path +// such as /usr/lib +// +// In Windows: +// The header files (colibri_api.h, etc) should be placed in the +// in the \vrpn\trivisio directory. +// colibri-api.lib will need to be located there as well. +// colibri-api.dll will need to be in the path or with the executable +// at run time. +/* #undef VRPN_USE_COLIBRIAPI */ + +//------------------------ +// Instructs VRPN to attempt to use HID. If you don't have libusb installed +// on Linux, you'll want to turn this off so that it doesn't fail to compile. +// This should work fine on Windows and Mac. +/* #undef VRPN_USE_HID */ + +//------------------------ +// Instructs VRPN to link in the source code to a local version of +// hidapi to access HID devices. The source code for this project +// is included as a git submodule under submodule/hidapi. To pull +// this down if it is not present, use the commands: +// 'git submodule init; git submodule update' from the vrpn directory. +// If you have a system hidapi and you prefer to use it, then do not +// define this here. Otherwise, define it so that VRPN will be able +// to access HID devices. +// Note that on Linux you will also need to have the libusb package +// installed in order to compile HIDAPI. You'll also need to uncomment +// the Makefile line in server_src that links with usb. +/* #undef VRPN_USE_LOCAL_HIDAPI */ + +//------------------------ +// Instructs VRPN to attempt to use LibUSB-1.0. This will compile and +// link servers that use USB directly (as opposed to those that use it +// through the HID interface). +// See http://libusb.sourceforge.net for more on LibUSB-1.0. +// Note that on Linux you will also need to have the libusb-1.0-0-dev +// package installed so that we can compile the code. You +// will also need to uncommment the SYSLIBS line for HID in the +// server_src/Makefile for this to link. +// Note that to compile on Windows you will need to have downloaded and +// installed +// the libusb.h file and libusb-1.0.lib files; the default location for +// the library is C:Program Files\libusb-1.0 and for the include file +// is in C:Program Files\libusb-1.0\libusb. To open a device on Windows, you +// will need to have installed a driver that lets LibUSB open the +// device. Generic HID devices and devices that use a WinUSB driver +// should work without adding a driver. If you need to add a driver, +// consider using the libUSB Zadig.exe program; do not do this for a +// HID device or a device that has another driver, as it can prevent the +// device from operating except through LibUSB. +// Note that on Linux you will also need to have the libusb-1.0-0-dev +// package installed so that we can compile the code. +/* #undef VRPN_USE_LIBUSB_1_0 */ + +// Instructs VRPN to compile code to handle JSON network messages. +// This requires jsoncpp. +// JSON Network (UDP) mesages are used by the vrpn widgets for Android, +/* #undef VRPN_USE_JSONNET */ + +//------------------------ +// Instructs VRPN to compile code to use the Arrington Research +// ViewPoint EyeTracker. You will also need to set VRPN_VIEWPOINT_H +// and VRPN_VIEWPOINT_LIB_PATH below to point to the correct location +// on your system. Note that the VRPN server and ViewPoint calibration +// software must use the same copy of the VPX_InterApp.dll +/* #undef VRPN_USE_VIEWPOINT */ +#define VRPN_VIEWPOINT_H "vpx.h" +/* #undef VRPN_VIEWPOINT_LIB_PATH */ + +//------------------------ +// Use DevInput devices. +/* #undef VRPN_USE_DEV_INPUT */ + +//------------------------- +// Use Linux kernel joystick support: +// note that using this kernel header +// makes the GPL apply to the server! +/* #undef VRPN_USE_JOYLIN */ + +//------------------------ +// Instructs VRPN to compile code to use the Polhemus Developer +// (PDI) library to enable opening several of their trackers using +// this interface (the G4 was the original one this was written +// for, but new versions are available for the Fastrak and Liberty). +/* #undef VRPN_USE_PDI */ + +//------------------------------------------------------------------// +// SYSTEM CONFIGURATION SECTION // +// EDIT THESE DEFINITIONS TO POINT TO OPTIONAL LIBRARIES. THEY ARE // +// USED BELOW TO LOCATE LIBRARIES AND INCLUDE FILES. // +//------------------------------------------------------------------// + +#define VRPN_SYSTEMDRIVE "C:" + +#define VRPN_PHASESPACE_LIB_PATH "../../phasespace/" + +#define VRPN_WIIUSE_H "wiiuse.h" + +#define VRPN_TRIVISIOCOLIBRI_H \ + "C:/Program Files/Trivisio/Colibri/include/TrivisioColibri.h" +#define VRPN_TRIVISIOCOLIBRI_LIB_PATH "C:/Program Files/Trivisio/Colibri/lib/" +#define VRPN_GHOST_31_PATH \ + VRPN_SYSTEMDRIVE "/Program Files/SensAble/GHOST/v3.1/lib/" +#define VRPN_GHOST_40_PATH \ + VRPN_SYSTEMDRIVE "/Program Files/SensAble/GHOST/v4.0/lib/" + +#define VRPN_NIDAQ_PATH \ + VRPN_SYSTEMDRIVE "/Program Files/National Instruments/NI-DAQ/Lib/" +#define VRPN_USDIGITAL_PATH VRPN_SYSTEMDRIVE "/Program Files/SEI Explorer/" + +//---------------------------------------------------------------// +// DO NOT EDIT BELOW THIS LINE FOR NORMAL CONFIGURATION SETTING. // +//---------------------------------------------------------------// + +// Use this macro in a file if it might be empty (compiling out completely) +// to squash Visual Studio warning LNK4221. +// Inspiration from +// http://stackoverflow.com/questions/1822887/what-is-the-best-way-to-eliminate-ms-visual-c-linker-warning-warning-lnk422 +#ifdef _MSC_VER +#define VRPN_SUPPRESS_EMPTY_OBJECT_WARNING() \ + namespace { \ + char vrpn_SuppressEmptyObjectDummy##__LINE__; \ + } +#else +#define VRPN_SUPPRESS_EMPTY_OBJECT_WARNING() +#endif + +// autolinking pragma only works/makes sense with MSVC +#ifdef _MSC_VER // [ + +// Load National Instruments libraries if we are using them. +// If this doesn't match where you have installed these libraries, +// edit the following lines to point at the correct libraries. Do +// this here rather than in the project settings so that it can be +// turned on and off using the definition above. +// NOTE: The paths to these libraries are set in the Settings/Link tab of +// the various project files. The paths to the include files are in the +// Settings/C++/preprocessor tab. +#ifdef VRPN_USE_NATIONAL_INSTRUMENTS +#pragma comment(lib, VRPN_NIDAQ_PATH "nidaq32.lib") +#pragma comment(lib, VRPN_NIDAQ_PATH "nidex32.lib") +#endif + +// Load US Digital libraries if we are using them. +// If this doesn't match where you have installed these libraries, +// edit the following lines to point at the correct libraries. Do +// this here rather than in the project settings so that it can be +// turned on and off using the definition above. +// NOTE: The paths to these libraries are set in the Settings/Link tab of +// the various project files. The paths to the include files are in the +// Settings/C++/preprocessor tab. +#ifdef VRPN_USE_USDIGITAL +#pragma comment(lib, VRPN_USDIGITAL_PATH "SEIDrv32.lib") +#endif + +// Load Microscribe-3D SDK libraries +// If this doesn't match where you have installed these libraries, +// edit the following lines to point at the correct libraries. Do +// this here rather than in the project settings so that it can be +// turned on and off using the definition above. +#ifdef VRPN_USE_MICROSCRIBE +#pragma comment(lib, "armdll32.lib") +#endif + +// Load Trivisio Colibri library +#ifdef VRPN_USE_TRIVISIOCOLIBRI +#pragma comment(lib, VRPN_TRIVISIOCOLIBRI_LIB_PATH "Trivisio.lib") +#endif + +#endif // ] _MSC_VER + +// This will be defined in the VRPN (non-DLL) project and nothing else +// Overrides USE_SHARED_LIBRARY to get rid of "inconsistent DLL linkage" +// warnings. +#ifdef VRPNDLL_NOEXPORTS +#undef VRPN_USE_SHARED_LIBRARY +#endif + +// This will be defined in the VRPN (DLL) project and nothing else +// Forces "USE_SHARED_LIBRARY independent of definition above so that the +// DLL will build +#if defined(VRPNDLL_EXPORTS) && !defined(VRPN_USE_SHARED_LIBRARY) +#define VRPN_USE_SHARED_LIBRARY +#endif + +// For client code, make sure we add the proper library dependency to the linker +#ifdef _WIN32 // [ +#ifdef _MSC_VER // [ +#ifdef VRPN_USE_WINSOCK2 +#pragma comment(lib, "ws2_32.lib") // VRPN requires the Windows Sockets library. +#else +#pragma comment(lib, \ + "wsock32.lib") // VRPN requires the Windows Sockets library. +#endif +#endif // ] _MSC_VER +#ifdef VRPN_USE_SHARED_LIBRARY +#ifdef VRPNDLL_EXPORTS +#define VRPN_API __declspec(dllexport) +#else +#define VRPN_API __declspec(dllimport) +#endif +#else +#define VRPN_API +#endif +#define VRPN_CALLBACK __stdcall +#else // ] WIN32 [ +// In the future, other architectures may need their own sections +#define VRPN_API +#define VRPN_CALLBACK +#endif // ] not WIN32 + +#define VRPN_CONFIGURE_H +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Connection.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Connection.h new file mode 100644 index 000000000000..e97e2c17bc7b --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Connection.h @@ -0,0 +1,1185 @@ +#ifndef VRPN_CONNECTION_H +#define VRPN_CONNECTION_H + +#include // for NULL, sprintf + +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK, etc +#include "vrpn_Shared.h" // for SOCKET, timeval +#include "vrpn_Types.h" // for vrpn_int32, vrpn_uint32, etc +#include "vrpn_EndpointContainer.h" + +#if !(defined(_WIN32) && defined(VRPN_USE_WINSOCK_SOCKETS)) +#include // for fd_set +#endif + +struct timeval; + +// Don't complain about using sprintf() when using Visual Studio. +#ifdef _MSC_VER +#pragma warning(disable : 4995 4996) +#endif + +/// This is the list of states that a connection can be in +/// (possible values for status). doing_okay() returns VRPN_TRUE +/// for connections > BROKEN. +enum vrpn_ConnectionStatus { + LISTEN = (1), + CONNECTED = (0), + COOKIE_PENDING = (-1), + TRYING_TO_CONNECT = (-2), + BROKEN = (-3), + LOGGING = (-4) +}; + +class VRPN_API + vrpn_File_Connection; // Forward declaration for get_File_Connection() + +/// @brief This structure is what is passed to a vrpn_Connection message +/// callback. +/// +/// It is used by objects, but not normally by user code. +struct vrpn_HANDLERPARAM { + vrpn_int32 type; + vrpn_int32 sender; + struct timeval msg_time; + vrpn_int32 payload_len; + const char *buffer; +}; + +/// @brief Type of a message handler for vrpn_Connection messages. +typedef int(VRPN_CALLBACK *vrpn_MESSAGEHANDLER)(void *userdata, + vrpn_HANDLERPARAM p); + +/// @brief Type of handler for filters on logfiles is the same as connection +/// handler +typedef vrpn_MESSAGEHANDLER vrpn_LOGFILTER; + +/// VRPN buffers are aligned on 8 byte boundaries so that we can pack and +/// unpack doubles into them on architectures that cannot handle unaligned +/// access. +const unsigned vrpn_ALIGN = 8; + +/// Types now have their storage dynamically allocated, so we can afford +/// to have large tables. We need at least 150-200 for the microscope +/// project as of Jan 98, and will eventually need two to three times that +/// number. +/// @{ +const int vrpn_CONNECTION_MAX_SENDERS = 2000; +const int vrpn_CONNECTION_MAX_TYPES = 2000; +/// @} + +/// @brief vrpn_ANY_SENDER can be used to register callbacks on a given message +/// type from any sender. + +const int vrpn_ANY_SENDER = -1; + +/// @brief vrpn_ANY_TYPE can be used to register callbacks for any USER type of +/// message from a given sender. System messages are handled separately. + +const int vrpn_ANY_TYPE = -1; + +/// @name Buffer lengths for TCP and UDP. +/// +/// TCP is an arbitrary number that can be changed by the user +/// using vrpn_Connection::set_tcp_outbuf_size(). +/// UDP is set based on Ethernet maximum transmission size; trying +/// to send a message via UDP which is longer than the MTU of any +/// intervening physical network may cause untraceable failures, +/// so for now we do not expose any way to change the UDP output +/// buffer size. (MTU = 1500 bytes, - 28 bytes of IP+UDP header) +/// @{ + +const int vrpn_CONNECTION_TCP_BUFLEN = 64000; +const int vrpn_CONNECTION_UDP_BUFLEN = 1472; +/// @} + +/// @brief Number of endpoints that a server connection can have. Arbitrary +/// limit. + +const int vrpn_MAX_ENDPOINTS = 256; + +/// @name System message types +/// @{ +const vrpn_int32 vrpn_CONNECTION_SENDER_DESCRIPTION = (-1); +const vrpn_int32 vrpn_CONNECTION_TYPE_DESCRIPTION = (-2); +const vrpn_int32 vrpn_CONNECTION_UDP_DESCRIPTION = (-3); +const vrpn_int32 vrpn_CONNECTION_LOG_DESCRIPTION = (-4); +const vrpn_int32 vrpn_CONNECTION_DISCONNECT_MESSAGE = (-5); +/// @} + +/// Classes of service for messages, specify multiple by ORing them together +/// Priority of satisfying these should go from the top down (RELIABLE will +/// override all others). +/// Most of these flags may be ignored, but RELIABLE is guaranteed +/// to be available. +/// @{ + +const vrpn_uint32 vrpn_CONNECTION_RELIABLE = (1 << 0); +const vrpn_uint32 vrpn_CONNECTION_FIXED_LATENCY = (1 << 1); +const vrpn_uint32 vrpn_CONNECTION_LOW_LATENCY = (1 << 2); +const vrpn_uint32 vrpn_CONNECTION_FIXED_THROUGHPUT = (1 << 3); +const vrpn_uint32 vrpn_CONNECTION_HIGH_THROUGHPUT = (1 << 4); + +/// @} + +/// @name What to log +/// @{ +const long vrpn_LOG_NONE = (0); +const long vrpn_LOG_INCOMING = (1 << 0); +const long vrpn_LOG_OUTGOING = (1 << 1); +/// @} + +// If defined, will filter out messages: if the remote side hasn't +// registered a type, messages of that type won't be sent over the +// link. WARNING: auto-type-registration breaks this. +//#define vrpn_FILTER_MESSAGES + +/// These are the strings that define the system-generated message +/// types that tell when connections are received and dropped. +/// @{ +extern VRPN_API const char *vrpn_got_first_connection; +extern VRPN_API const char *vrpn_got_connection; +extern VRPN_API const char *vrpn_dropped_connection; +extern VRPN_API const char *vrpn_dropped_last_connection; +/// @} + +/// @brief vrpn_CONTROL is the sender used for notification messages sent to the +/// user +/// from the local VRPN implementation (got_first_connection, etc.) +/// and for control messages sent by auxiliary services. (Such as +/// class vrpn_Controller, which will be introduced in a future revision.) + +extern VRPN_API const char *vrpn_CONTROL; + +/// @brief Length of names within VRPN +typedef char cName[100]; + +/// Placed here so vrpn_FileConnection can use it too. +struct VRPN_API vrpn_LOGLIST { + vrpn_HANDLERPARAM data; + vrpn_LOGLIST *next; + vrpn_LOGLIST *prev; +}; + +class VRPN_API vrpn_Endpoint_IP; +class VRPN_API vrpn_Connection; + +/// @brief Function pointer to an endpoint allocator. +typedef vrpn_Endpoint_IP *(*vrpn_EndpointAllocator)( + vrpn_Connection *connection, vrpn_int32 *numActiveConnections); + +namespace vrpn { + + /// @brief Combines the function pointer for an Endpoint Allocator with its + /// two arguments into a single callable object, with the ability to + /// override the last parameter at call time. + class BoundEndpointAllocator { + public: + BoundEndpointAllocator() + : epa_(NULL) + , conn_(NULL) + , numActiveEndpoints_(NULL) + { + } + BoundEndpointAllocator(vrpn_EndpointAllocator epa, + vrpn_Connection *conn, + vrpn_int32 *numActiveEndpoints = NULL) + : epa_(epa) + , conn_(conn) + , numActiveEndpoints_(numActiveEndpoints) + { + } + + typedef vrpn_Endpoint_IP *return_type; + + /// @brief Default, fully pre-bound + return_type operator()() const + { + if (!epa_) { + return NULL; + } + return (*epa_)(conn_, numActiveEndpoints_); + } + + /// @brief Overload, with alternate num active connnection pointer. + return_type operator()(vrpn_int32 *alternateNumActiveEndpoints) const + { + if (!epa_) { + return NULL; + } + return (*epa_)(conn_, alternateNumActiveEndpoints); + } + + private: + vrpn_EndpointAllocator epa_; + vrpn_Connection *conn_; + vrpn_int32 *numActiveEndpoints_; + }; +} // namespace vrpn +/// @todo HACK +/// These structs must be declared outside of vrpn_Connection +/// (although we'd like to make them protected/private members) +/// because aCC on PixelFlow doesn't handle nested classes correctly. +/// @{ + +/// @brief Description of a callback entry for a user type. +struct vrpnMsgCallbackEntry { + vrpn_MESSAGEHANDLER handler; ///< Routine to call + void *userdata; ///< Passed along + vrpn_int32 sender; ///< Only if from sender + vrpnMsgCallbackEntry *next; ///< Next handler +}; + +struct vrpnLogFilterEntry { + vrpn_LOGFILTER filter; ///< routine to call + void *userdata; ///< passed along + vrpnLogFilterEntry *next; +}; +/// @} + +class VRPN_API vrpn_Connection; +class VRPN_API vrpn_Log; +class VRPN_API vrpn_TranslationTable; +class VRPN_API vrpn_TypeDispatcher; + +/// @brief Encapsulation of the data and methods for a single generic connection +/// to take care of one part of many clients talking to a single server. +/// +/// This will only be used from within the vrpn_Connection class; it should +/// not be instantiated by users or devices. +/// Should not be visible! + +class VRPN_API vrpn_Endpoint { + +public: + vrpn_Endpoint(vrpn_TypeDispatcher *dispatcher, + vrpn_int32 *connectedEndpointCounter); + virtual ~vrpn_Endpoint(void); + + /// @name Accessors + /// @{ + + /// Returns the local mapping for the remote type (-1 if none). + int local_type_id(vrpn_int32 remote_type) const; + + /// Returns the local mapping for the remote sender (-1 if none). + int local_sender_id(vrpn_int32 remote_sender) const; + + virtual vrpn_bool doing_okay(void) const = 0; + /// @} + + /// @name Manipulators + /// @{ + + void init(void); + + virtual int mainloop(timeval *timeout) = 0; + + /// Clear out the remote mapping list. This is done when a + /// connection is dropped and we want to try and re-establish + /// it. + void clear_other_senders_and_types(void); + + /// A new local sender or type has been established; set + /// the local type for it if the other side has declared it. + /// Return 1 if the other side has one, 0 if not. + int newLocalSender(const char *name, vrpn_int32 which); + int newLocalType(const char *name, vrpn_int32 which); + + /// Adds a new remote type/sender and returns its index. + /// Returns -1 on error. + /// @{ + int newRemoteType(cName type_name, vrpn_int32 remote_id, + vrpn_int32 local_id); + int newRemoteSender(cName sender_name, vrpn_int32 remote_id, + vrpn_int32 local_id); + /// @} + + /// Pack a message that will be sent the next time mainloop() is called. + /// Turn off the RELIABLE flag if you want low-latency (UDP) send. + virtual int pack_message(vrpn_uint32 len, struct timeval time, + vrpn_int32 type, vrpn_int32 sender, + const char *buffer, + vrpn_uint32 class_of_service) = 0; + + /// send pending report, clear the buffer. + /// This function was protected, now is public, so we can use it + /// to send out intermediate results without calling mainloop + virtual int send_pending_reports(void) = 0; + + int pack_log_description(void); + ///< Packs the log description set by setup_new_connection(). + + virtual int setup_new_connection(void) = 0; + ///< Sends the magic cookie and other information to its + ///< peer. It is called by both the client and server setup routines. + + virtual void poll_for_cookie(const timeval *timeout = NULL) = 0; + virtual int finish_new_connection_setup(void) = 0; + + virtual void drop_connection(void) = 0; + ///< Should only be called by vrpn_Connection::drop_connection(), + ///< since there's more housecleaning to do at that level. I suppose + ///< that argues against separating this function out. + + virtual void clearBuffers(void) = 0; + ///< Empties out the TCP and UDP send buffers. + ///< Needed by vrpn_FileConnection to get at {udp,tcp}NumOut. + + int pack_sender_description(vrpn_int32 which); + ///< Packs a sender description over our socket. + + int pack_type_description(vrpn_int32 which); + ///< Packs a type description. + + /// @} + int status; + + /// @todo XXX These should be protected; making them so will lead to making + /// the code split the functions between Endpoint and Connection + /// protected: + + long d_remoteLogMode; ///< Mode to put the remote logging in + char *d_remoteInLogName; ///< Name of the remote log file + char *d_remoteOutLogName; ///< Name of the remote log file + + ///< Name of the remote host we are connected to. This is kept for + ///< informational purposes. It is printed by the ceiling server, + ///< for example. + char rhostname[150]; + + /// @name Logging + /// + /// TCH 19 April 00; changed into two logs 16 Feb 01 + /// @{ + + vrpn_Log *d_inLog; + vrpn_Log *d_outLog; + + void setLogNames(const char *inName, const char *outName); + int openLogs(void); + /// @} + + /// @name Routines that handle system messages + /// + /// Visible so that vrpn_Connection can pass them to the Dispatcher + /// @{ + static int VRPN_CALLBACK + handle_sender_message(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_type_message(void *userdata, vrpn_HANDLERPARAM p); + /// @} + + /// @name Routines to inform the endpoint of the connection of + /// which it is a part. + /// @{ + void setConnection(vrpn_Connection *conn) { d_parent = conn; } + vrpn_Connection *getConnection() { return d_parent; } + /// @} + +protected: + virtual int dispatch(vrpn_int32 type, vrpn_int32 sender, timeval time, + vrpn_uint32 payload_len, char *bufptr); + + int tryToMarshall(char *outbuf, vrpn_int32 &buflen, vrpn_int32 &numOut, + vrpn_uint32 len, timeval time, vrpn_int32 type, + vrpn_int32 sender, const char *buffer, + vrpn_uint32 classOfService); + ///< Calls marshall_message(); if that fails, calls + ///< send_pending_reports() and then marshalls again. + ///< Returns the number of characters successfully marshalled. + + int marshall_message(char *outbuf, vrpn_uint32 outbuf_size, + vrpn_uint32 initial_out, vrpn_uint32 len, + struct timeval time, vrpn_int32 type, + vrpn_int32 sender, const char *buffer, + vrpn_uint32 sequenceNumber); + + // The senders and types we know about that have been described by + // the other end of the connection. Also, record the local mapping + // for ones that have been described with the same name locally. + // The arrays are indexed by the ID from the other side, and store + // the name and local ID that corresponds to each. + + vrpn_TranslationTable *d_senders; + vrpn_TranslationTable *d_types; + + vrpn_TypeDispatcher *d_dispatcher; + vrpn_int32 *d_connectionCounter; + + vrpn_Connection *d_parent; +}; + +/// @brief Encapsulation of the data and methods for a single IP-based +/// connection +/// to take care of one part of many clients talking to a single server. +/// +/// This will only be used from within the vrpn_Connection_IP class; it should +/// not be instantiated by users or devices. +/// Should not be visible! + +class VRPN_API vrpn_Endpoint_IP : public vrpn_Endpoint { + +public: + vrpn_Endpoint_IP(vrpn_TypeDispatcher *dispatcher, + vrpn_int32 *connectedEndpointCounter); + virtual ~vrpn_Endpoint_IP(void); + + /// @name Accessors + /// @{ + virtual vrpn_bool doing_okay(void) const; + + /// True if the UDP outbound is open, False if not. + vrpn_bool outbound_udp_open(void) const; + + vrpn_int32 tcp_outbuf_size(void) const; + vrpn_int32 udp_outbuf_size(void) const; + /// @} + + /// @name Manipulators + /// @{ + + void init(void); + + int mainloop(timeval *timeout); + + /// @brief Pack a message that will be sent the next time mainloop() is + /// called. + /// + /// Turn off the RELIABLE flag if you want low-latency (UDP) send. + int pack_message(vrpn_uint32 len, struct timeval time, vrpn_int32 type, + vrpn_int32 sender, const char *buffer, + vrpn_uint32 class_of_service); + + /// @brief send pending report, clear the buffer. + /// + /// This function was protected, now is public, so we can use it + /// to send out intermediate results without calling mainloop + virtual int send_pending_reports(void); + + int pack_udp_description(int portno); + + int handle_tcp_messages(const timeval *timeout); + int handle_udp_messages(const timeval *timeout); + + int connect_tcp_to(const char *msg); + int connect_tcp_to(const char *addr, int port); + ///< Connects d_tcpSocket to the specified address (msg = "IP port"); + ///< sets status to COOKIE_PENDING; returns 0 on success, -1 on failure + int connect_udp_to(const char *addr, int port); + ///< Connects d_udpSocket to the specified address and port; + ///< returns 0 on success, sets status to BROKEN and returns -1 + ///< on failure. + + vrpn_int32 set_tcp_outbuf_size(vrpn_int32 bytecount); + + int setup_new_connection(void); + ///< Sends the magic cookie and other information to its + ///< peer. It is called by both the client and server setup routines. + + void poll_for_cookie(const timeval *timeout = NULL); + int finish_new_connection_setup(void); + + void drop_connection(void); + ///< Should only be called by vrpn_Connection::drop_connection(), + ///< since there's more housecleaning to do at that level. I suppose + ///< that argues against separating this function out. + + void clearBuffers(void); + ///< Empties out the TCP and UDP send buffers. + ///< Needed by vrpn_FileConnection to get at {udp,tcp}NumOut. + + void setNICaddress(const char *); + + /// @todo XXX These should be protected; making them so will lead to making + /// the code split the functions between Endpoint and Connection + /// protected: + + SOCKET d_tcpSocket; + + /// This section deals with when a client connection is trying to + /// establish (or re-establish) a connection with its server. It + /// keeps track of what we need to know to make this happen. + + SOCKET d_tcpListenSocket; + int d_tcpListenPort; + ///< Socket and port that the client listens on + ///< when lobbing datagrams at the server and + ///< waiting for it to call back. + + /// Socket to use to lob UDP requests asking for the server to + /// call us back. + SOCKET d_udpLobSocket; + + char *d_remote_machine_name; ///< Machine to call + int d_remote_port_number; ///< Port to connect to on remote machine + timeval d_last_connect_attempt; ///< When the last UDP lob occurred + + vrpn_bool d_tcp_only; + ///< For connections made through firewalls or NAT with the + ///< tcp: URL, we do not want to allow the endpoints on either + ///< end to open a UDP link to their counterparts. If this is + ///< the case, then this flag should be set to true. + +protected: + int getOneTCPMessage(int fd, char *buf, size_t buflen); + int getOneUDPMessage(char *buf, size_t buflen); + + SOCKET d_udpOutboundSocket; + SOCKET d_udpInboundSocket; + ///< Inbound unreliable messages come here. + ///< Need one for each due to different + ///< clock synchronization for each; we + ///< need to know which server each message is from. + ///< @todo XXX Now that we don't need multiple clocks, can we collapse this? + + char *d_tcpOutbuf; + char *d_udpOutbuf; + vrpn_int32 d_tcpBuflen; + vrpn_int32 d_udpBuflen; + vrpn_int32 d_tcpNumOut; + vrpn_int32 d_udpNumOut; + + vrpn_int32 d_tcpSequenceNumber; + vrpn_int32 d_udpSequenceNumber; + + vrpn_float64 + d_tcpAlignedInbuf[vrpn_CONNECTION_TCP_BUFLEN / sizeof(vrpn_float64) + + 1]; + vrpn_float64 + d_udpAlignedInbuf[vrpn_CONNECTION_UDP_BUFLEN / sizeof(vrpn_float64) + + 1]; + char *d_tcpInbuf; + char *d_udpInbuf; + + char *d_NICaddress; +}; + +/// @brief Generic connection class not specific to the transport mechanism. +/// +/// It abstracts all of the common functions. Specific implementations +/// for IP, MPI, and other transport mechanisms follow. +class VRPN_API vrpn_Connection { + +protected: + /// Constructor for server connection. This cannot be called + /// directly any more because vrpn_Connection is an abstract base + /// class. Call vrpn_create_server_connection() to make a server + /// of arbitrary type based on a name. + vrpn_Connection(const char *local_in_logfile_name, + const char *local_out_logfile_name, + vrpn_EndpointAllocator epa = allocateEndpoint); + + /// Constructor for client connection. This cannot be called + /// directly because vrpn_Connection is an abstract base class. + /// Call vrpn_get_connection_by_name() to create a client connection. + vrpn_Connection(const char *local_in_logfile_name, + const char *local_out_logfile_name, + const char *remote_in_logfile_name, + const char *remote_out_logfile_name, + vrpn_EndpointAllocator epa = allocateEndpoint); + +public: + virtual ~vrpn_Connection(void); + + /// Returns vrpn_true if the connection is okay, vrpn_false if not + virtual vrpn_bool doing_okay(void) const; + + /// Returns vrpn_true if the connection has been established, vrpn_false if + /// not + /// (For a networkless connection, this is equivalent to doing_okay()). + virtual vrpn_bool connected(void) const; + + /// This function returns the logfile names of this connection in + /// the parameters. It will allocate memory for the name of each + /// log file in use. If no logging of a particular type is happening, + /// then *(X_Y_logname) will be set to NULL. + /// IMPORTANT: code calling this function is responsible for freeing + /// the memory allocated for these strings. + void get_log_names(char **local_in_logname, char **local_out_logname, + char **remote_in_logname, char **remote_out_logname); + + /// Call each time through program main loop to handle receiving any + /// incoming messages and sending any packed messages. + /// Returns -1 when connection dropped due to error, 0 otherwise. + /// (only returns -1 once per connection drop). + /// Optional argument is TOTAL time to block on select() calls; + /// there may be multiple calls to select() per call to mainloop(), + /// and this timeout will be divided evenly between them. + virtual int mainloop(const struct timeval *timeout = NULL) = 0; + + /// Get a token to use for the string name of the sender or type. + /// Remember to check for -1 meaning failure. + virtual vrpn_int32 register_sender(const char *name); + virtual vrpn_int32 register_message_type(const char *name); + + /// Set up (or remove) a handler for a message of a given type. + /// Optionally, specify which sender to handle messages from. + /// Handlers will be called during mainloop(). + /// Your handler should return 0 or a communication error is assumed + /// and the connection will be shut down. + virtual int register_handler(vrpn_int32 type, vrpn_MESSAGEHANDLER handler, + void *userdata, + vrpn_int32 sender = vrpn_ANY_SENDER); + virtual int unregister_handler(vrpn_int32 type, vrpn_MESSAGEHANDLER handler, + void *userdata, + vrpn_int32 sender = vrpn_ANY_SENDER); + + /// Pack a message that will be sent the next time mainloop() is called. + /// Turn off the RELIABLE flag if you want low-latency (UDP) send. + virtual int pack_message(vrpn_uint32 len, struct timeval time, + vrpn_int32 type, vrpn_int32 sender, + const char *buffer, vrpn_uint32 class_of_service); + + /// send pending report, clear the buffer. + /// This function was protected, now is public, so we can use it + /// to send out intermediate results without calling mainloop + virtual int send_pending_reports(void) = 0; + + /// Returns the time since the connection opened. + /// Some subclasses may redefine time. + virtual int time_since_connection_open(struct timeval *elapsed_time); + + /// returns the current time in the connection (since the epoch -- UTC + /// time). + virtual timeval get_time(); + + /// Returns the name of the specified sender/type, or NULL + /// if the parameter is invalid. Only works for user + /// messages (type >= 0). + virtual const char *sender_name(vrpn_int32 sender); + virtual const char *message_type_name(vrpn_int32 type); + + /// @brief Sets up a filter function for logging. + /// Any user message to be logged is first passed to this function, + /// and will only be logged if the function returns zero (XXX). + /// NOTE: this only affects local logging - remote logging + /// is unfiltered! Only user messages are filtered; all system + /// messages are logged. + /// Returns nonzero on failure. + virtual int register_log_filter(vrpn_LOGFILTER filter, void *userdata); + + /// Save any messages on any endpoints which have been logged so far. + virtual int save_log_so_far(); + + /// vrpn_File_Connection implements this as "return this" so it + /// can be used to detect a File_Connection and get the pointer for it + virtual vrpn_File_Connection *get_File_Connection(void); + + /// This function should be seldom used. It is here for the case of + /// the vrpn_Imager, whose servers do not follow "The VRPN Way" because + /// they try to jam more data into the network than there is bandwidth + /// to support it. As a result, a client may call mainloop() on the + /// connection and have it never return -- there is always more data + /// in the network to read, so we never hand control back to the main + /// program. The reason for the name comes from an old U.S. cartoon + /// called "The Jetsons". In it, George Jetson is running on a + /// treadmill when it goes out of control and starts spinning so fast + /// that he can't even run fast enough to reach the controls and turn + /// it off. He cries out to his wife, "Jane! Stop this crazy thing!" + /// The parameter specifies a trigger: if more than the specified number + /// of messages come in on a given input channel during one mainloop() + /// call, the connection should stop looking for more messages. NOTE: + /// this does not guarantee that only this many messages will be received, + /// only that the connection will stop looking for new ones on a given + /// channel once that many have been received (for example, UDP channels + /// will parse all the rest of the messages in a packet before stopping). + /// A value of 0 turns off the limit, and will cause all incoming messages + /// to be handled before returning. + void Jane_stop_this_crazy_thing(vrpn_uint32 stop_looking_after) + { + d_stop_processing_messages_after = stop_looking_after; + }; + vrpn_uint32 get_Jane_value(void) + { + return d_stop_processing_messages_after; + }; + +protected: + /// If this value is greater than zero, the connection should stop + /// looking for new messages on a given endpoint after this many + /// are found. + vrpn_uint32 d_stop_processing_messages_after; + + int connectionStatus; ///< Status of the connection + + /// Redefining this and passing it to constructors + /// allows a subclass to use a different subclass of Endpoint. + /// It should do NOTHING but return an endpoint + /// of the appropriate class; it may not access subclass data, + /// since it'll be called from a constructor + static vrpn_Endpoint_IP *allocateEndpoint(vrpn_Connection *, + vrpn_int32 *connectedEC); + +#ifdef _MSC_VER +#pragma warning(push) +// Disable "need dll interface" warning on these members +#pragma warning(disable : 4251) +#endif + /// Function object wrapping an endpoint allocator and binding its + /// arguments. + vrpn::BoundEndpointAllocator d_boundEndpointAllocator; + + /// Sockets used to talk to remote Connection(s) + /// and other information needed on a per-connection basis + vrpn::EndpointContainer d_endpoints; + +#ifdef _MSC_VER +#pragma warning(pop) +#endif + vrpn_int32 d_numConnectedEndpoints; + ///< We need to track the number of connected endpoints separately + ///< to properly send out got-first-connection/dropped-last-connection + ///< messages. This value is *managed* by the Endpoints, but we + ///< need exactly one copy per Connection, so it's on the Connection. + + /// @brief Routines that handle system messages + /// @{ + static int VRPN_CALLBACK + handle_log_message(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_disconnect_message(void *userdata, vrpn_HANDLERPARAM p); + /// @} + +private: + void init(vrpn_EndpointAllocator + epa); ///< Base initialization for all constructors. +protected: + int delete_endpoint(vrpn_Endpoint *endpoint); + int compact_endpoints(void); + + virtual int pack_sender_description(vrpn_int32 which); + ///< Send the sender description to ALL endpoints. + + virtual int pack_type_description(vrpn_int32 which); + ///< Send the type description to ALL endpoints. + + virtual int do_callbacks_for(vrpn_int32 type, vrpn_int32 sender, + struct timeval time, vrpn_uint32 len, + const char *buffer); + + /// Returns message type ID, or -1 if unregistered + int message_type_is_registered(const char *) const; + + /// Timekeeping - TCH 30 June 98 + timeval start_time; + + // + /// Counting references to this connection. +public: + void addReference(); + void removeReference(); + +private: + int d_references; + + // + /// Specify whether this connection should be deleted automatically when + /// it is no longer need (reference count reaches zero). + /// For connections created by the VRPN code (as is done in + /// get_connection_by_name) these should be auto-deleted. + /// Connections created by user code should not be auto-deleted; + /// that is up to the user to decide when finished. + /// By default, the constructor sets this to FALSE. + /// VRPN code (or user code) can set this to TRUE if it wants the + /// connection to be deleted automatically when the last service on it + /// is deleted +public: + void setAutoDeleteStatus(bool setvalue) { d_autoDeleteStatus = setvalue; } + +private: + bool d_autoDeleteStatus; ///< FALSE by default. + +public: + /// Derived classes need access to d_dispatcher in their + /// allocateEndpoint() routine. Several compilers won't give it to + /// them, even if they do inherit publicly. Until we figure that + /// out, d_dispatcher needs to be public. + + vrpn_TypeDispatcher *d_dispatcher; + +protected: + int doSystemCallbacksFor(vrpn_HANDLERPARAM, void *); + + /// Server logging w. multiconnection - TCH July 00 + /// Use one "hidden" endpoint for outgoing logs (?), + /// standard per-endpoint logs with augmented names for incoming. + /// To make a hidden endpoint we create d_endpoints[0] and increment + /// the d_numEndpoints, but DON'T pass it d_numConnectedEndpoints + /// (although it should be safe to do so, since it should never truly + /// become connected, but we might have to "fake" it to get it to log + /// correctly). + + // vrpn_Endpoint * d_serverLogEndpoint; + int d_serverLogCount; + vrpn_int32 d_serverLogMode; + char *d_serverLogName; + + vrpn_bool d_updateEndpoint; + + virtual void updateEndpoints(void); + ///< This function will be called on the mainloop() iteration + ///< after *d_endpointAllocator is called, which lets subclasses + ///< do initialization. (They can't do so during allocateEndpoint + ///< because it's called during the Connection constructor when + ///< their constructors haven't executed yet.) +}; + +class VRPN_API vrpn_Connection_IP : public vrpn_Connection { + +protected: + /// Make a client connection. To access this from user code, + /// call vrpn_get_connection_by_name(). + /// Create a connection - if server_name is not a file: name, + /// makes an SDI-like connection to the named remote server + /// (otherwise functions as a non-networked messaging hub). + /// Port less than zero forces default. + /// Currently, server_name is an extended URL that defaults + /// to VRPN connections at the port, but can be file:: to read + /// from a file. Other extensions should maintain this, so + /// that VRPN uses URLs to name things that are to be connected + /// to. + vrpn_Connection_IP(const char *server_name, + int port = vrpn_DEFAULT_LISTEN_PORT_NO, + const char *local_in_logfile_name = NULL, + const char *local_out_logfile_name = NULL, + const char *remote_in_logfile_name = NULL, + const char *remote_out_logfile_name = NULL, + const char *NIC_IPaddress = NULL, + vrpn_EndpointAllocator epa = allocateEndpoint); + +public: + /// Make a server that listens for client connections. + /// DEPRECATED: Call vrpn_create_server_connection() with the + /// NIC name and port number you want. + vrpn_Connection_IP( + unsigned short listen_port_no = vrpn_DEFAULT_LISTEN_PORT_NO, + const char *local_in_logfile_name = NULL, + const char *local_out_logfile_name = NULL, + const char *NIC_IPaddress = NULL, + vrpn_Endpoint_IP *(*epa)(vrpn_Connection *, + vrpn_int32 *) = allocateEndpoint); + + virtual ~vrpn_Connection_IP(void); + + /// This is similar to check connection except that it can be + /// used to receive requests from before a server starts up + virtual int connect_to_client(const char *machine, int port); + + /// Call each time through program main loop to handle receiving any + /// incoming messages and sending any packed messages. + /// Returns -1 when connection dropped due to error, 0 otherwise. + /// (only returns -1 once per connection drop). + /// Optional argument is TOTAL time to block on select() calls; + /// there may be multiple calls to select() per call to mainloop(), + /// and this timeout will be divided evenly between them. + virtual int mainloop(const struct timeval *timeout = NULL); + +protected: + /// If this value is greater than zero, the connection should stop + /// looking for new messages on a given endpoint after this many + /// are found. + vrpn_uint32 d_stop_processing_messages_after; + +protected: + friend VRPN_API vrpn_Connection *vrpn_get_connection_by_name( + const char *cname, const char *local_in_logfile_name, + const char *local_out_logfile_name, const char *remote_in_logfile_name, + const char *remote_out_logfile_name, const char *NIC_IPaddress, + bool force_connection); + friend VRPN_API vrpn_Connection * + vrpn_create_server_connection(const char *cname, + const char *local_in_logfile_name, + const char *local_out_logfile_name); + + /// @name Only used for a vrpn_Connection that awaits incoming connections + /// @{ + SOCKET listen_udp_sock; ///< UDP Connect requests come here + SOCKET listen_tcp_sock; ///< TCP Connection requests come here + /// @} + + /// Routines that handle system messages + static int VRPN_CALLBACK + handle_UDP_message(void *userdata, vrpn_HANDLERPARAM p); + + /// @brief Called by all constructors + void init(void); + + /// @brief send pending report, clear the buffer. + /// + /// This function was protected, now is public, so we can use it + /// to send out intermediate results without calling mainloop + virtual int send_pending_reports(void); + + //// This is called by a server-side process to see if there have + //// been any UDP packets come in asking for a connection. If there + //// are, it connects the TCP port and then calls handle_connection(). + virtual void + server_check_for_incoming_connections(const struct timeval *timeout = NULL); + + /// This routine is called by a server-side connection when a + /// new connection has just been established, and the tcp port + /// has been connected to it. + virtual void handle_connection(vrpn_Endpoint *endpoint); + + /// Drops the connection with the given, non-NULL endpoint. Depending on if + /// we're a server or a client, this may result in the endpoints needing + /// compacting once you're no longer iterating on the endpoint container. + virtual void drop_connection(vrpn_Endpoint *endpoint); + + /// Like drop_connection, except it includes the call to compact the + /// endpoints. Only safe to call if you can guarantee no iterators are open + /// to the container, since compact invalidates them. + void drop_connection_and_compact(vrpn_Endpoint *endpoint); + + char *d_NIC_IP; +}; + +/// @brief Constructor for a Loopback connection that will basically just +/// pass messages between objects that are connected to it. It offers no +/// external connections, via IP or any other mechanism. It is useful +/// if you want to make the client and server in the same connection and +/// you don't need to have anything else connect. + +class VRPN_API vrpn_Connection_Loopback : public vrpn_Connection { + +protected: + /// Make a client connection. To access this from user code, + /// call vrpn_create_server_connection() with a service name + /// of 'loopback:'. + /// For now, we don't enable logging on a Loopback connection. + vrpn_Connection_Loopback(); + +public: + virtual ~vrpn_Connection_Loopback(void); + + /// Call each time through program main loop to handle receiving any + /// incoming messages and sending any packed messages. + /// Returns -1 on error, 0 otherwise. + /// Optional argument is TOTAL time to block on select() calls; + /// there may be multiple calls to select() per call to mainloop(), + /// and this timeout will be divided evenly between them. + virtual int mainloop(const struct timeval *timeout = NULL); + + /// Returns vrpn_true if the connection is okay, vrpn_false if not + virtual vrpn_bool doing_okay(void) const { return vrpn_true; } + + /// Returns vrpn_true if the connection has been established, vrpn_false if + /// not + /// (For a networkless connection, this is equivalent to doing_okay()). + virtual vrpn_bool connected(void) const { return vrpn_true; } + +protected: + friend VRPN_API vrpn_Connection * + vrpn_create_server_connection(const char *cname, + const char *local_in_logfile_name, + const char *local_out_logfile_name); + + /// @brief send pending report, clear the buffer. + /// + /// This function was protected, now is public, so we can use it + /// to send out intermediate results without calling mainloop + virtual int send_pending_reports(void) { return 0; } +}; + +/// @brief Create a client connection of arbitrary type (VRPN UDP/TCP, TCP, +/// File, Loopback, MPI). +/// +/// WARNING: May not be thread safe. +/// If no IP address for the NIC to use is specified, uses the default +/// NIC. If the force_reopen flag is set, a new connection will be +/// made even if there was already one to that server. +/// When done with the object, call removeReference() on it (which will +/// delete it if there are no other references). +VRPN_API vrpn_Connection *vrpn_get_connection_by_name( + const char *cname, const char *local_in_logfile_name = NULL, + const char *local_out_logfile_name = NULL, + const char *remote_in_logfile_name = NULL, + const char *remote_out_logfile_name = NULL, + const char *NIC_IPaddress = NULL, bool force_reopen = false); + +/// @brief Create a server connection of arbitrary type (VRPN UDP/TCP, +/// TCP, File, Loopback, MPI). +/// +/// Returns NULL if the name is not understood or the connection cannot +/// be created. +/// WARNING: May not be thread safe. +/// To create a VRPN TCP/UDP server, use a name like: +/// vrpn:machine_name_or_ip:port +/// machine_name_or_ip:port +/// machine_name_or_ip +/// :port (This port on any network card.) +/// To create an MPI server, use a name like: +/// mpi:MPI_COMM_WORLD +/// mpi:comm_number +/// When done with the object, call removeReference() on it (which will +/// delete it if there are no other references). +VRPN_API vrpn_Connection * +vrpn_create_server_connection(const char *cname, + const char *local_in_logfile_name = NULL, + const char *local_out_logfile_name = NULL); + +/// Lets you make one with the default settings, or just ask for a specific +/// port number on the default NIC on this machine. This matches the +/// signature on the old constructor to make it easier to port existing +/// servers. +inline VRPN_API vrpn_Connection * +vrpn_create_server_connection(int port = vrpn_DEFAULT_LISTEN_PORT_NO, + const char *local_in_logfile_name = NULL, + const char *local_out_logfile_name = NULL, + const char *NIC_NAME = NULL) +{ + char name[256]; + if (NIC_NAME == NULL) { + sprintf(name, ":%d", port); + } + else { + sprintf(name, "%s:%d", NIC_NAME, port); + } + return vrpn_create_server_connection(name, local_in_logfile_name, + local_out_logfile_name); +} + +/// @name Utility routines to parse names (@) +/// Both return new char [], and it is the caller's responsibility +/// to delete this memory! +/// @{ +VRPN_API char *vrpn_copy_service_name(const char *fullname); +VRPN_API char *vrpn_copy_service_location(const char *fullname); +/// @} + +/// @brief Utility routines to parse file specifiers FROM service locations +/// +/// file: +/// +/// file:/// +/// +/// file:/// +VRPN_API char *vrpn_copy_file_name(const char *filespecifier); + +/// @name Utility routines to parse host specifiers FROM service locations +/// +/// +/// +/// : +/// +/// x-vrpn:// +/// +/// x-vrpn://: +/// +/// x-vrsh:///, +/// +/// The caller is responsible for calling delete [] on the returned character +/// pointer if it is not NULL. +/// @{ +VRPN_API char *vrpn_copy_machine_name(const char *hostspecifier); +VRPN_API int vrpn_get_port_number(const char *hostspecifier); +VRPN_API char *vrpn_copy_rsh_program(const char *hostspecifier); +VRPN_API char *vrpn_copy_rsh_arguments(const char *hostspecifier); +/// @} + +/// @brief Utility routine to rename the service name of a given host specifier. +char *vrpn_set_service_name(const char *specifier, const char *newServiceName); + +/// Checks the buffer to see if it is a valid VRPN header cookie. +/// Returns -1 on total mismatch, +/// 1 on minor version mismatch or other acceptable difference, +/// and 0 on exact match. +/// @{ +VRPN_API int check_vrpn_cookie(const char *buffer); +VRPN_API int check_vrpn_file_cookie(const char *buffer); +/// @} + +/// @brief Returns the size of the magic cookie buffer, plus any alignment +/// overhead. +VRPN_API size_t vrpn_cookie_size(void); + +VRPN_API int write_vrpn_cookie(char *buffer, size_t length, + long remote_log_mode); + +/// @name Utility routines for reading from and writing to sockets/file +/// descriptors +/// @{ +#ifndef VRPN_USE_WINSOCK_SOCKETS +int VRPN_API +vrpn_noint_block_write(int outfile, const char buffer[], size_t length); +int VRPN_API vrpn_noint_block_read(int infile, char buffer[], size_t length); +int VRPN_API vrpn_noint_select(int width, fd_set *readfds, fd_set *writefds, + fd_set *exceptfds, struct timeval *timeout); +#else /* winsock sockets */ +int VRPN_API +vrpn_noint_block_write(SOCKET outsock, char *buffer, size_t length); +int VRPN_API vrpn_noint_block_read(SOCKET insock, char *buffer, size_t length); +#endif /* VRPN_USE_WINSOCK_SOCKETS */ + /// @} + +/** + * @brief Singleton class that keeps track of all known VRPN connections + * and makes sure they're deleted on shutdown. + * + * We make it static to guarantee that the destructor is called + * on program close so that the destructors of all the vrpn_Connections + * that have been allocated are called so that all open logs are flushed + * to disk. Each connection should add itself to this list in its + * constructor and should remove itself from this list in its + * destructor. + */ + +// This section holds data structures and functions to open +// connections by name. +// The intention of this section is that it can open connections for +// objects that are in different libraries (trackers, buttons and sound), +// even if they all refer to the same connection. +// Even though each individual vrpn_Connection class is not yet thread +// safe, so should only have its methods called from a single thread, +// the vrpn_ConnectionManager should be thread safe to allow connections +// to be created and destroyed by different threads. + +class VRPN_API vrpn_ConnectionManager { + +public: + ~vrpn_ConnectionManager(void); + + /// @brief The only way to get access to an instance of this class. + /// Guarantees that there is only one, global object. + /// Also guarantees that it will be constructed the first time + /// this function is called, and (hopefully?) destructed when + /// the program terminates. + static vrpn_ConnectionManager &instance(void); + + /// NB implementation is not particularly efficient; we expect + /// to have O(10) connections, not O(1000). + /// @{ + void addConnection(vrpn_Connection *, const char *name); + void deleteConnection(vrpn_Connection *); + /// @} + + /// Searches through d_kcList but NOT d_anonList + /// (Connections constructed with no name) + vrpn_Connection *getByName(const char *name); + +private: + /// Mutex to ensure thread safety; + vrpn_Semaphore d_semaphore; + + struct knownConnection { + char name[1000]; + vrpn_Connection *connection; + knownConnection *next; + }; + + /// @brief named connections + knownConnection *d_kcList; + + /// @brief unnamed (server) connections + knownConnection *d_anonList; + + vrpn_ConnectionManager(void); + + // @brief copy constructor undefined to prevent instantiations + vrpn_ConnectionManager(const vrpn_ConnectionManager &); + + void deleteConnection(vrpn_Connection *, knownConnection **); +}; + +#endif // VRPN_CONNECTION_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Dial.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Dial.h new file mode 100644 index 000000000000..829d67368e2d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Dial.h @@ -0,0 +1,116 @@ +// vrpn_Dial.h +// This implements a Dial class. A dial is an object that spins, +// possibly without bound. It returns the fraction of a revolution that +// it has turned as its message type. + +#ifndef VRPN_DIAL_H +#define VRPN_DIAL_H + +const int vrpn_DIAL_MAX = 128; + +#include // for NULL + +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_float64, vrpn_int32 + +class VRPN_API vrpn_Connection; +struct vrpn_HANDLERPARAM; + +class VRPN_API vrpn_Dial : public vrpn_BaseClass { +public: + vrpn_Dial(const char *name, vrpn_Connection *c = NULL); + +protected: + vrpn_float64 dials[vrpn_DIAL_MAX]; + vrpn_int32 num_dials; + struct timeval timestamp; + vrpn_int32 change_m_id; // change message id + + virtual int register_types(void); + virtual vrpn_int32 encode_to(char *buf, vrpn_int32 buflen, vrpn_int32 dial, + vrpn_float64 delta); + virtual void report_changes(void); // send report iff changed + virtual void report(void); // send report +}; + +//---------------------------------------------------------- +// Example server for an array of dials +// This will generate an array of dials that all spin at the same +// rate (revolutions/second), and which send reports at a different rate +// (updates/second). A real server would send reports whenever it saw +// dials changing, and would not have the spin_rate or update_rate parameters. +// This server can be used for testing to make sure a client is +// working correctly, and to ensure that a connection to a remote server +// is working (by running the example server with the name of the device that +// the real server would use). + +class VRPN_API vrpn_Dial_Example_Server : public vrpn_Dial { +public: + vrpn_Dial_Example_Server(const char *name, vrpn_Connection *c, + vrpn_int32 numdials = 1, + vrpn_float64 spin_rate = 1.0, + vrpn_float64 update_rate = 10.0); + virtual void mainloop(); + +protected: + vrpn_float64 _spin_rate; // The rate at which to spin (revolutions/sec) + vrpn_float64 _update_rate; // The rate at which to update (reports/sec) + // The dials[] array within the parent is used for the values + // The num_dials within the parent is used + // The timestamp field within the parent structure is used for timing + // The report_changes() or report() functions within the parent are used +}; + +//---------------------------------------------------------- +//************** Users deal with the following ************* + +// User routine to handle a change in dial values. This is called when +// the dial callback is called (when a message from its counterpart +// across the connetion arrives). + +typedef struct _vrpn_DIALCB { + struct timeval msg_time; // Timestamp when change happened + vrpn_int32 dial; // which dial changed + vrpn_float64 change; // Fraction of a revolution it changed +} vrpn_DIALCB; + +typedef void(VRPN_CALLBACK *vrpn_DIALCHANGEHANDLER)(void *userdata, + const vrpn_DIALCB info); + +// Open a dial device that is on the other end of a connection +// and handle updates from it. This is the type of device +// that user code will deal with. + +class VRPN_API vrpn_Dial_Remote : public vrpn_Dial { +public: + // The name of the device to connect to. + // Optional argument to be used when the Remote MUST listen on + // a connection that is already open. + vrpn_Dial_Remote(const char *name, vrpn_Connection *c = NULL); + ~vrpn_Dial_Remote(); + + // This routine calls the mainloop of the connection it's on + virtual void mainloop(); + + // (un)Register a callback handler to handle dial updates + virtual int register_change_handler(void *userdata, + vrpn_DIALCHANGEHANDLER handler) + { + return d_callback_list.register_handler(userdata, handler); + }; + virtual int unregister_change_handler(void *userdata, + vrpn_DIALCHANGEHANDLER handler) + { + return d_callback_list.unregister_handler(userdata, handler); + } + +protected: + vrpn_Callback_List d_callback_list; + + static int VRPN_CALLBACK + handle_change_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_EndpointContainer.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_EndpointContainer.h new file mode 100644 index 000000000000..d58f952aba74 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_EndpointContainer.h @@ -0,0 +1,364 @@ +/** @file + @brief Header + + @date 2015 + + @author + Ryan Pavlik + Sensics, Inc. + +*/ + +// Copyright 2015 Sensics, Inc. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef INCLUDED_vrpn_EndpointContainer_h_GUID_DB073DE8_5BBC_46BF_255B_71264D47A639 +#define INCLUDED_vrpn_EndpointContainer_h_GUID_DB073DE8_5BBC_46BF_255B_71264D47A639 + +// Internal Includes +#include "vrpn_Types.h" +#include "vrpn_Configure.h" + +#include "vrpn_Assert.h" + +// Library/third-party includes +// - none + +// Standard includes +#include +#include // for NULL + +class VRPN_API vrpn_Endpoint; +class VRPN_API vrpn_Endpoint_IP; + +namespace vrpn { + + class EndpointIterator; + + /** @brief Container for endpoints, held by pointer. + + To check if we have room, use this: `if (d_endpoints.full()) {}` instead of + the old code looking like this: `if (which_end >= vrpn_MAX_ENDPOINTS)` + + Usage example for iteration: + + ~~~ + for (vrpn::EndpointIterator it = d_endpoints.begin(), e = d_endpoints.end(); + it != e; ++it) { + it->pack_type_description(which) + } + ~~~ + + */ + class EndpointContainer { + public: + typedef vrpn_Endpoint_IP T; + typedef T &reference; + typedef T *pointer; + typedef vrpn_Endpoint *base_pointer; + + private: + typedef std::vector container_type; + + public: + typedef container_type::size_type size_type; + typedef EndpointIterator iterator; + typedef EndpointIterator const_iterator; + + /// @brief Constructor of empty container. + EndpointContainer(); + + /// @brief Destructor - includes a call to clear() + ~EndpointContainer(); + + /// @brief Tells each held endpoint in turn to drop the connection then + /// deletes it + void clear(); + + /// @brief Shorthand for get_by_index(0) + pointer front() const { return get_by_index(0); } + + /// @brief Given the result of an endpoint allocator, if it's non-NULL, + /// takes ownership of it. + /// @return the input pointer + template T *acquire(T *endpoint) + { + acquire_(endpoint); + return endpoint; + } + + /// @brief Goes through and gets rid of the NULL entries. + void compact(); + + /// @brief Can we no longer accommodate a new endpoint? + bool full() const; + + /// @brief Checks to see if an index is both in-range and pointing to a + /// still-extant object + bool is_valid(size_type i) const; + + /// @brief Destroys the contained endpoint by address. + /// @return true if there was something for us to delete + bool destroy(base_pointer endpoint); + + pointer get_by_index(size_type i) const; + + /// @brief Get size of container including NULL elements that haven't + /// been compacted yet. + size_type get_full_container_size() const; + + /// @brief Get an iterator to the beginning that skips nulls. + /// Invalidated by compacting. + iterator begin() const; + + /// @brief Get an iterator suitable only for testing to see if we're + /// "done" + iterator end() const; + + private: + /// @name Internal raw iterators and methods + /// @{ + typedef container_type::iterator raw_iterator; + typedef container_type::const_iterator raw_const_iterator; + raw_iterator begin_() { return container_.begin(); } + raw_const_iterator begin_() const { return container_.begin(); } + raw_iterator end_() { return container_.end(); } + raw_const_iterator end_() const { return container_.end(); } + // @} + /// @name Internal helper methods + /// @{ + /// @brief Implementation of acquire for the stored pointer type. + void acquire_(pointer endpoint); + /// @brief Do actual compact once we've determined it's necessary. + void compact_(); + + /// @} + container_type container_; + bool needsCompact_; + }; + +#define VRPN_ECITERATOR_ASSERT_INVARIANT() \ + VRPN_ASSERT_MSG(valid() != equal_to_default_(), \ + "Class invariant for EndpointIterator") + + /// @brief An iterator that goes forward in an EndpointContainer skipping + /// the NULLs, that also acts a bit like a pointer/smart pointer (can treat + /// it as a vrpn_Endpoint *) + /// + /// Because we know at design time that it iterates through pointers, + /// we have pointer-related operator overloads that mean there's no need to + /// double-dereference. + /// + /// Fulfills the InputIterator concept: + /// http://en.cppreference.com/w/cpp/concept/InputIterator + /// + /// All end() iterators compare equal to each other and to the + /// default-constructed iterator. They are the only invalid iterators: + /// incrementing an iterator past the end makes it the same as the + /// default-constructed iterator. + /// + /// That is, for all EndpointIterators it, we enforce the class invariant + /// `it.valid() || (it == EndpointIterator())` (and that's actually an XOR) + class EndpointIterator { + public: + // typedef EndpointIteratorBase type; + typedef EndpointIterator type; + typedef EndpointContainer const container_type; + typedef container_type::pointer pointer; + typedef container_type::reference reference; + typedef container_type::size_type size_type; + + /// @brief Default constructor, equal to all other default-constructed + /// instances and all end() + EndpointIterator() + : index_(0) + , container_(NULL) + { + VRPN_ASSERT_MSG(equal_to_default_(), + "Default constructed value should be equal to " + "default: verifies that 'equal_to_default_()' is " + "equivalent to '*this == EndpointIterator()'"); + VRPN_ASSERT_MSG(!valid(), + "Default constructed value should not be valid"); + } + + /// @brief Constructor with container, points to beginning of container. + EndpointIterator(container_type &container) + : index_(0) + , container_(&container) + { + // Advance index as required to maintain the class invariant. + skip_nulls_(); + VRPN_ECITERATOR_ASSERT_INVARIANT(); + } + + /// @brief Constructor with container and raw index into container. + EndpointIterator(container_type &container, size_type index) + : index_(index) + , container_(&container) + { + // Advance index as required to maintain the class invariant. + skip_nulls_(); + VRPN_ECITERATOR_ASSERT_INVARIANT(); + } + + /// @brief Does this iterator refer to a valid element? + /// + /// Class invariant: valid() || (*this == type()) + /// That is, there is only one invalid value. + bool valid() const + { + return container_ && container_->is_valid(index_); + } + + /// @brief Extract the pointer (NULL if iterator is invalid) + pointer get_pointer() const + { + VRPN_ECITERATOR_ASSERT_INVARIANT(); + // Only need to condition on container validity: invalid indexes + // safely return null from get_raw_() + return container_ ? (get_raw_()) : NULL; + } + + /// @brief Implicit conversion operator to pointer. + operator pointer() const + { + VRPN_ECITERATOR_ASSERT_INVARIANT(); + return get_pointer(); + } + + /// @brief prefix ++ operator, increments (and skips any nulls) + type &operator++() + { + /// Invariant might be invalid here, since the user might have just + /// deleted something. + if (equal_to_default_()) { + // Early out if we're already the end sentinel (default + // constructor value) + return *this; + } + + // Increment until we either go out of bounds or get a non-null + // entry + index_++; + skip_nulls_(); + VRPN_ECITERATOR_ASSERT_INVARIANT(); + return *this; + } + + /// @name Smart pointer idiom operators + /// @{ + pointer operator->() const + { + VRPN_ECITERATOR_ASSERT_INVARIANT(); + return get_pointer(); + } + + reference operator*() const + { + VRPN_ECITERATOR_ASSERT_INVARIANT(); + return *get_raw_(); + } + /// @} + + /// @name Comparison operators, primarily for loop use + /// @{ + bool operator==(type const &other) const + { + return (container_ == other.container_) && (index_ == other.index_); + } + bool operator!=(type const &other) const + { + return (container_ != other.container_) || (index_ != other.index_); + } + /// @} + + private: + bool equal_to_default_() const + { + return (NULL == container_) && (index_ == 0); + } + void skip_nulls_() + { + while (index_in_bounds_() && (get_raw_() == NULL)) { + index_++; + } + // We may have run out of elements, so check the invariant + enforce_invariant_(); + } + /// @brief Function to verify an iterator to enforce the class + /// invariant. + void enforce_invariant_() + { + if (!valid()) { + /// Assign from default-constructed iterator, to be the same as + /// end() + *this = type(); + } + } + + /// @brief get, without checking validity of container_ first! + /// + /// Note that the container handles cases where the index is out of + /// range by returning NULL, so that's safe. + pointer get_raw_() const { return container_->get_by_index(index_); } + + /// @brief Helper to check index vs container bounds, without checking + /// validity of container_ first! + bool index_in_bounds_() const + { + return index_ < container_->get_full_container_size(); + } + + size_type index_; + container_type *container_; + }; +#undef VRPN_ECITERATOR_ASSERT_INVARIANT + + // Inline Implementations // + + inline bool + EndpointContainer::is_valid(EndpointContainer::size_type i) const + { + return (i < get_full_container_size()) && (NULL != container_[i]); + } + + inline EndpointContainer::pointer + EndpointContainer::get_by_index(size_type i) const + { + if (!is_valid(i)) { + return NULL; + } + return container_[i]; + } + + inline EndpointContainer::size_type + EndpointContainer::get_full_container_size() const + { + return container_.size(); + } + + // making this condition inline so that it has minimal overhead if + // we don't actually need to perform a compaction. + inline void EndpointContainer::compact() + { + if (needsCompact_) { + compact_(); + } + } + + inline EndpointIterator EndpointContainer::begin() const + { + return EndpointIterator(*this); + } + + inline EndpointIterator EndpointContainer::end() const + { + return EndpointIterator(); + } + +} // namespace vrpn + +#endif // INCLUDED_vrpn_EndpointContainer_h_GUID_DB073DE8_5BBC_46BF_255B_71264D47A639 diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FileConnection.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FileConnection.h new file mode 100644 index 000000000000..be0a44d99f20 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FileConnection.h @@ -0,0 +1,326 @@ +#ifndef VRPN_FILE_CONNECTION_H +#define VRPN_FILE_CONNECTION_H + +// {{{ vrpn_File_Connection +// +// Tom Hudson, June 1998 + +// This class *reads* a file written out by vrpn_Connection's logging hooks. + +// The interface exactly matches that of vrpn_Connection. To do things that +// are meaningful on log replay but not on live networks, create a +// vrpn_File_Controller and pass your vrpn_File_Connection to its constructor, +// or just ask the Connection for its file connection pointer and do the +// operations directly on the FileConnection if the pointer is non-NULL. + +// Logfiles are recorded as *sent*, not as translated by the receiver, +// so we still need to have all the correct names for senders and types +// registered. + +// September 1998: by default preloads the entire log file on startup. +// This causes a delay (nontrivial for large logs) but should help smooth +// playback. +// }}} + +#include // for NULL, FILE + +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Connection.h" // for vrpn_LOGLIST (ptr only), etc +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_float32, vrpn_int32, etc + +struct timeval; + +// Global variable used to indicate whether File Connections should +// pre-load all of their records into memory when opened. This is the +// default behavior, but fails on very large files that eat up all +// of the memory. This defaults to "true". User code should set this +// to "false" before calling vrpn_get_connection_by_name() or creating +// a new vrpn_File_Connection object if it wants that file connection +// to not preload. The value is only checked at connection creation time; +// the connection behaves consistently once created. This operation is +// useful for applications that load large data files and don't want to +// wait for them to pre-load. + +extern VRPN_API bool vrpn_FILE_CONNECTIONS_SHOULD_PRELOAD; + +// Global variable used to indicate whether File Connections should +// keep already-read messages stored in memory. If not, then we have +// to re-load the file starting at the beginning on rewind. This +// defaults to "true". User code should set this +// to "false" before calling vrpn_get_connection_by_name() or creating +// a new vrpn_File_Connection object if it wants that file connection +// to not preload. The value is only checked at connection creation time; +// the connection behaves consistently once created. This operation is +// useful for applications that read through large data files and +// don't have enough memory to keep them in memory at once, or for applications +// that read through only once and have no need to go back and check. + +extern VRPN_API bool vrpn_FILE_CONNECTIONS_SHOULD_ACCUMULATE; + +// Global variable used to indicate whether File Connections should +// play through all system messages and get to the first user message +// when opened or reset to the beginning. This defaults to "true". +// User code should set this +// to "false" before calling vrpn_get_connection_by_name() or creating +// a new vrpn_File_Connection object if it wants that file connection +// to not preload. The value is only checked at connection creation time; +// the connection behaves consistently once created. Leaving this true +// can help with offsets in time that happen at the beginning of files. + +extern VRPN_API bool vrpn_FILE_CONNECTIONS_SHOULD_SKIP_TO_USER_MESSAGES; + +class VRPN_API vrpn_File_Connection : public vrpn_Connection { +public: + vrpn_File_Connection(const char *station_name, + const char *local_in_logfile_name = NULL, + const char *local_out_logfile_name = NULL); + virtual ~vrpn_File_Connection(void); + + virtual int mainloop(const timeval *timeout = NULL); + + // returns the elapsed time in the file + virtual int time_since_connection_open(timeval *elapsed_time); + + // returns the current time in the file since the epoch (UTC time). + virtual timeval get_time() { return d_time; } + + virtual vrpn_File_Connection *get_File_Connection(void); + + // Pretend to send pending report, really just clear the buffer. + virtual int send_pending_reports(void); + + // {{{ fileconnections-specific methods (playback control) +public: + // XXX the following should not be public if we want vrpn_File_Connection + // to have the same interface as vrpn_Connection + // + // If so handler functions for messages for these operations + // should be made, and functions added to vrpn_File_Controller which + // generate the messages. This seemed like it would be messy + // since most of these functions have return values + + // rate of 0.0 is paused, 1.0 is normal speed + void set_replay_rate(vrpn_float32 rate) + { + d_filetime_accum.set_replay_rate(rate); + } + + vrpn_float32 get_replay_rate() { return d_filetime_accum.replay_rate(); } + + // resets to the beginning of the file + // returns 0 on success + int reset(void); + + // returns 1 if we're at the end of file + int eof(); + + // end_time for play_to_time() is an elapsed time + // returns -1 on error or EOF, 0 on success + int play_to_time(vrpn_float64 end_time); + int play_to_time(timeval end_time); + + // end_filetime is an absolute time, corresponding to the + // timestamps of the entries in the file, + // returns -1 on error or EOF, 0 on success + int play_to_filetime(const timeval end_filetime); + + // plays the next entry, returns -1 or error or EOF, 0 otherwise + int playone(); + + // plays at most one entry, but won't play past end_filetime + // returns 0 on success, 1 if at end_filetime, -1 on error or EOF + int playone_to_filetime(timeval end_filetime); + + // returns the elapsed time of the file + timeval get_length(); + double get_length_secs(); + + // returns the timestamp of the earliest in time user message + timeval get_lowest_user_timestamp(); + + // returns the timestamp of the greatest-in-time user message + timeval get_highest_user_timestamp(); + + // returns the name of the file + const char *get_filename(); + + // jump_to_time sets the current position to the given elapsed time + // return 1 if we got to the specified time and 0 if we didn't + int jump_to_time(vrpn_float64 newtime); + int jump_to_time(timeval newtime); + + // jump_to_filetime sets the current position to the given absolute time + // return 1 if we got to the specified time and 0 if we didn't + int jump_to_filetime(timeval absolute_time); + + // Limits the number of messages played out on any one call to mainloop. + // 0 => no limit. + // Used to stop continuous callback-handling when messages arrive + // at a very high rate (such as from a vrpn_Imager) or to make sure + // that we are able to pause after each frame in frame-by-frame + // playback for tracking analysis programs. + void limit_messages_played_back(vrpn_uint32 max_playback) + { + Jane_stop_this_crazy_thing(max_playback); + }; + + // }}} + // {{{ tokens for VRPN control messages (data members) +protected: + vrpn_int32 d_controllerId; + + vrpn_int32 d_set_replay_rate_type; + vrpn_int32 d_reset_type; + vrpn_int32 d_play_to_time_type; + // long d_jump_to_time_type; + + // }}} + // {{{ time-keeping +protected: + timeval d_last_told; // Last time we printed error about no open file. + timeval d_time; // current time in file + timeval d_start_time; // time of first record in file + timeval d_earliest_user_time; // time of first user message + vrpn_bool d_earliest_user_time_valid; + timeval d_highest_user_time; // time of last user message + vrpn_bool d_highest_user_time_valid; + + // finds the timestamps of the earliest and highest-time user messages + void find_superlative_user_times(); + + // these are to be used internally when jumping around in the + // stream (e.g., for finding the earliest and latest timed + // user messages). They assume + // 1) that only functions such as advance_currentLogEntry, + // read_entry and manual traversal of d_logHead/d_logTail + // will be used. + // the functions return false if they don't save or restore the bookmark + class VRPN_API vrpn_FileBookmark { + public: + vrpn_FileBookmark(); + ~vrpn_FileBookmark(); + bool valid; + timeval oldTime; + long int file_pos; // ftell result + vrpn_LOGLIST *oldCurrentLogEntryPtr; // just a pointer, useful for accum + // or preload + vrpn_LOGLIST *oldCurrentLogEntryCopy; // a deep copy, useful for + // no-accum, no-preload + }; + bool store_stream_bookmark(); + bool return_to_bookmark(); + vrpn_FileBookmark d_bookmark; + + // wallclock time at the (beginning of the) last call + // to mainloop that played back an event + timeval d_last_time; // XXX remove + + class VRPN_API FileTime_Accumulator { + // accumulates the amount of time that we will advance + // filetime by when we next play back messages. + timeval d_filetime_accum_since_last_playback; + + // wallclock time when d_filetime_accum_since_last_playback + // was last updated + timeval d_time_of_last_accum; + + // scale factor between stream time and wallclock time + vrpn_float32 d_replay_rate; + + public: + FileTime_Accumulator(); + + // return accumulated time since last reset + const timeval &accumulated(void) + { + return d_filetime_accum_since_last_playback; + } + + // return last time accumulate_to was called + const timeval &time_of_last_accum(void) { return d_time_of_last_accum; } + + vrpn_float32 replay_rate(void) { return d_replay_rate; } + + // add (d_replay_rate * (now_time - d_time_of_last_accum)) + // to d_filetime_accum_since_last_playback + // then set d_time_of_last_accum to now_time + void accumulate_to(const timeval &now_time); + + // if current rate is non-zero, then time is accumulated + // before d_replay_rate is set to new_rate + void set_replay_rate(vrpn_float32 new_rate); + + // set d_time_of_last_accum to now_time + // and set d_filetime_accum_since_last_playback to zero + void reset_at_time(const timeval &now_time); + }; + FileTime_Accumulator d_filetime_accum; + + // }}} + // {{{ actual mechanics of the logfile +protected: + char *d_fileName; + FILE *d_file; + + void play_to_user_message(); + + // helper function for mainloop() + int need_to_play(timeval filetime); + + // checks the cookie at + // the head of the log file; + // exit on error! + virtual int read_cookie(void); + + virtual int read_entry(void); // appends entry to d_logTail + // returns 0 on success, 1 on EOF, -1 on error + + // Steps the currentLogEntry pointer forward one. + // It handles both cases of preload and non-preload. + // returns 0 on success, 1 on EOF, -1 on error + virtual int advance_currentLogEntry(void); + + virtual int close_file(void); + + // }}} + // {{{ handlers for VRPN control messages that might come from + // a File Controller object that wants to control this + // File Connection. +protected: + static int VRPN_CALLBACK handle_set_replay_rate(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_reset(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_play_to_time(void *, vrpn_HANDLERPARAM); + + // }}} + // {{{ Maintains a doubly-linked list structure that keeps + // copies of the messages from the file in memory. If + // d_accumulate is false, then there is only ever one entry + // in memory (d_currentLogEntry == d_logHead == d_logTail). + // If d_preload is true, then all of the records from the file + // are read into the list in the constructor and we merely step + // through memory when playing the streamfile. If d_preload is + // false and d_accumulate is true, then we have all of the + // records up the d_currentLogEntry in memory (d_logTail points + // to d_currentLogEntry but not to the last entry in the file + // until we get to the end of the file). + // The d_currentLogEntry should always be non-NULL unless we are + // past the end of all messages... we will either have preloaded + // all of them or else the read routine will attempt to load the + // next message each time one is played. The constructor fills it + // in with the first message, which makes it non-NULL initially. + // HOWEVER, if there are no user messages and we're asked to skip + // to the first user message then it can be NULL right after the + // constructor is called. +protected: + vrpn_LOGLIST *d_logHead; // the first read-in record + vrpn_LOGLIST *d_logTail; // the last read-in record + vrpn_LOGLIST *d_currentLogEntry; // Message that we've just loaded, or are + // at right now + vrpn_LOGLIST *d_startEntry; // potentially after initial system messages + bool d_preload; // Should THIS File Connection pre-load? + bool d_accumulate; // Should THIS File Connection accumulate? + // }}} +}; + +#endif // VRPN_FILE_CONNECTION_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FileController.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FileController.h new file mode 100644 index 000000000000..1e19c06337eb --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FileController.h @@ -0,0 +1,47 @@ +#ifndef VRPN_FILE_CONTROLLER_H +#define VRPN_FILE_CONTROLLER_H +#include "vrpn_Configure.h" // for VRPN_API +#include "vrpn_Types.h" // for vrpn_int32, vrpn_float32 + +class VRPN_API vrpn_Connection; // from vrpn_Connection.h + +// class vrpn_File_Controller +// Tom Hudson, July 1998 + +// Controls a file connection (logfile playback). +// Can be attached to any vrpn_Connection. +// vrpn_File_Connections will respond to the messages. + +class VRPN_API vrpn_File_Controller { + +public: + vrpn_File_Controller(vrpn_Connection *); + ~vrpn_File_Controller(void); + + void set_replay_rate(vrpn_float32 = 1.0); + // Sets the rate at which the file is replayed. + + void reset(void); + // Returns to the beginning of the file. + // Does NOT reset rate to 1.0. + // Equivalent to set_to_time(< 0L, 0L >) + + void play_to_time(struct timeval t); + // Goes to an arbitrary elapsed time t in the file, + // triggering all events between the current time and t. + // Does not work in the past (use reset() first). + + // void jump_to_time (struct timeval t); + +protected: + vrpn_Connection *d_connection; + + vrpn_int32 d_myId; + + vrpn_int32 d_set_replay_rate_type; + vrpn_int32 d_reset_type; + vrpn_int32 d_play_to_time_type; + // long d_jump_to_time_type; +}; + +#endif // VRPN_FILE_CONTROLLER_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_ForceDevice.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_ForceDevice.h new file mode 100644 index 000000000000..c3cbc4df5e49 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_ForceDevice.h @@ -0,0 +1,730 @@ +#ifndef FORCEDEVICE_H +#define FORCEDEVICE_H + +#include // for NULL + +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_int32, vrpn_float32, etc + +class VRPN_API vrpn_Connection; +struct vrpn_HANDLERPARAM; + +#define MAXPLANE 4 // maximum number of planes in the scene + +// for recovery: +#define DEFAULT_NUM_REC_CYCLES (10) + +// possible values for errorCode: +#define FD_VALUE_OUT_OF_RANGE 0 // surface parameter out of range +#define FD_DUTY_CYCLE_ERROR 1 // servo loop is taking too long +#define FD_FORCE_ERROR 2 // max force exceeded, or motors overheated + // or amplifiers not enabled +#define FD_MISC_ERROR 3 // everything else +#define FD_OK 4 // no error + +// If defined, springs are implemented in the client as force fields. +// If not, springs are implemented with special messages +// and extra Ghost classes. Either way support for the messages +// is compiled into the parent class so that servers can support +// both kinds of clients. + +// (Springs as force fields require some knotty mathematical programming +// at the clients that I can't seem to get right, but avoid lots of +// extra message types and an awful lot of bug-prone Ghost.) + +#define FD_SPRINGS_AS_FIELDS + +class VRPN_API vrpn_ForceDevice : public vrpn_BaseClass { + +public: + vrpn_ForceDevice(const char *name, vrpn_Connection *c); + virtual ~vrpn_ForceDevice(void); + + void print_report(void); + void print_plane(void); + + void setSurfaceKspring(vrpn_float32 k) { SurfaceKspring = k; } + void setSurfaceKdamping(vrpn_float32 d) { SurfaceKdamping = d; } + void setSurfaceFstatic(vrpn_float32 ks) { SurfaceFstatic = ks; } + void setSurfaceFdynamic(vrpn_float32 kd) { SurfaceFdynamic = kd; } + void setRecoveryTime(int rt) { numRecCycles = rt; } + + // additional surface properties + void setSurfaceKadhesionNormal(vrpn_float32 k) + { + SurfaceKadhesionNormal = k; + } + void setSurfaceKadhesionLateral(vrpn_float32 k) + { + SurfaceKadhesionLateral = k; + } + void setSurfaceBuzzFrequency(vrpn_float32 freq) { SurfaceBuzzFreq = freq; } + void setSurfaceBuzzAmplitude(vrpn_float32 amp) { SurfaceBuzzAmp = amp; } + void setSurfaceTextureWavelength(vrpn_float32 wl) + { + SurfaceTextureWavelength = wl; + } + void setSurfaceTextureAmplitude(vrpn_float32 amp) + { + SurfaceTextureAmplitude = amp; + } + + void setCustomEffect(vrpn_int32 effectId, vrpn_float32 *params = NULL, + vrpn_uint32 nbParams = 0); + + void setFF_Origin(vrpn_float32 x, vrpn_float32 y, vrpn_float32 z) + { + ff_origin[0] = x; + ff_origin[1] = y; + ff_origin[2] = z; + } + void setFF_Origin(vrpn_float32 x[3]) + { + ff_origin[0] = x[0]; + ff_origin[1] = x[1]; + ff_origin[2] = x[2]; + } + void setFF_Force(vrpn_float32 fx, vrpn_float32 fy, vrpn_float32 fz) + { + ff_force[0] = fx; + ff_force[1] = fy; + ff_force[2] = fz; + } + void setFF_Force(vrpn_float32 f[3]) + { + ff_force[0] = f[0]; + ff_force[1] = f[1]; + ff_force[2] = f[2]; + } + void setFF_Jacobian(vrpn_float32 dfxdx, vrpn_float32 dfxdy, + vrpn_float32 dfxdz, vrpn_float32 dfydx, + vrpn_float32 dfydy, vrpn_float32 dfydz, + vrpn_float32 dfzdx, vrpn_float32 dfzdy, + vrpn_float32 dfzdz) + { + ff_jacobian[0][0] = dfxdx; + ff_jacobian[0][1] = dfxdy; + ff_jacobian[0][2] = dfxdz; + ff_jacobian[1][0] = dfydx; + ff_jacobian[1][1] = dfydy; + ff_jacobian[1][2] = dfydz; + ff_jacobian[2][0] = dfzdx; + ff_jacobian[2][1] = dfzdy; + ff_jacobian[2][2] = dfzdz; + } + void setFF_Radius(vrpn_float32 r) { ff_radius = r; } + + void set_plane(vrpn_float32 *p); + void set_plane(vrpn_float32 *p, vrpn_float32 d); + void set_plane(vrpn_float32 a, vrpn_float32 b, vrpn_float32 c, + vrpn_float32 d); + + void sendError(int error_code); + + int getRecoveryTime(void) { return numRecCycles; } + int connectionAvailable(void) { return (d_connection != NULL); } + + // constants for constraint messages + + enum ConstraintGeometry { + NO_CONSTRAINT, + POINT_CONSTRAINT, + LINE_CONSTRAINT, + PLANE_CONSTRAINT + }; + +protected: + virtual int register_types(void); + + vrpn_int32 force_message_id; // ID of force message to connection + vrpn_int32 plane_message_id; // ID of plane equation message + vrpn_int32 plane_effects_message_id; // additional plane properties + vrpn_int32 forcefield_message_id; // ID of force field message + vrpn_int32 scp_message_id; // ID of surface contact point message + + // constraint messages + + vrpn_int32 enableConstraint_message_id; + vrpn_int32 setConstraintMode_message_id; + vrpn_int32 setConstraintPoint_message_id; + vrpn_int32 setConstraintLinePoint_message_id; + vrpn_int32 setConstraintLineDirection_message_id; + vrpn_int32 setConstraintPlanePoint_message_id; + vrpn_int32 setConstraintPlaneNormal_message_id; + vrpn_int32 setConstraintKSpring_message_id; + // vrpn_int32 set_constraint_message_id;// ID of constraint force message + + // XXX - error messages should be put into the vrpn base class + // whenever someone makes one + + vrpn_int32 error_message_id; // ID of force device error message + + // IDs for trimesh messages + + vrpn_int32 addObject_message_id; + vrpn_int32 addObjectExScene_message_id; + vrpn_int32 moveToParent_message_id; + vrpn_int32 setObjectPosition_message_id; + vrpn_int32 setObjectOrientation_message_id; + vrpn_int32 setObjectScale_message_id; + vrpn_int32 removeObject_message_id; + vrpn_int32 setVertex_message_id; + vrpn_int32 setNormal_message_id; + vrpn_int32 setTriangle_message_id; + vrpn_int32 removeTriangle_message_id; + vrpn_int32 updateTrimeshChanges_message_id; + vrpn_int32 transformTrimesh_message_id; + vrpn_int32 setTrimeshType_message_id; + vrpn_int32 clearTrimesh_message_id; + + // IDs for scene messages + vrpn_int32 setHapticOrigin_message_id; + vrpn_int32 setHapticScale_message_id; + vrpn_int32 setSceneOrigin_message_id; + vrpn_int32 getNewObjectID_message_id; + vrpn_int32 setObjectIsTouchable_message_id; + + // ajout ONDIM + vrpn_int32 custom_effect_message_id; + // fni ajout ONDIM + + // ENCODING + // ajout ONDIM + static char *encode_custom_effect(vrpn_int32 &len, vrpn_uint32 effectId, + const vrpn_float32 *params, + vrpn_uint32 nbParams); + // fin ajout ONDIM + static char *encode_force(vrpn_int32 &length, const vrpn_float64 *force); + static char *encode_scp(vrpn_int32 &length, const vrpn_float64 *pos, + const vrpn_float64 *quat); + static char *encode_plane(vrpn_int32 &length, const vrpn_float32 *plane, + const vrpn_float32 kspring, + const vrpn_float32 kdamp, const vrpn_float32 fdyn, + const vrpn_float32 fstat, + const vrpn_int32 plane_index, + const vrpn_int32 n_rec_cycles); + static char *encode_surface_effects(vrpn_int32 &len, + const vrpn_float32 k_adhesion_norm, + const vrpn_float32 k_adhesion_lat, + const vrpn_float32 tex_amp, + const vrpn_float32 tex_wl, + const vrpn_float32 buzz_amp, + const vrpn_float32 buzz_freq); + static char *encode_vertex(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_int32 vertNum, const vrpn_float32 x, + const vrpn_float32 y, const vrpn_float32 z); + static char *encode_normal(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_int32 vertNum, const vrpn_float32 x, + const vrpn_float32 y, const vrpn_float32 z); + static char *encode_triangle(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_int32 triNum, + const vrpn_int32 vert0, const vrpn_int32 vert1, + const vrpn_int32 vert2, const vrpn_int32 norm0, + const vrpn_int32 norm1, + const vrpn_int32 norm2); + static char *encode_removeTriangle(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_int32 triNum); + static char *encode_updateTrimeshChanges(vrpn_int32 &len, + const vrpn_int32 objNum, + const vrpn_float32 kspring, + const vrpn_float32 kdamp, + const vrpn_float32 fdyn, + const vrpn_float32 fstat); + static char *encode_setTrimeshType(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_int32 type); + static char *encode_trimeshTransform(vrpn_int32 &len, + const vrpn_int32 objNum, + const vrpn_float32 homMatrix[16]); + + //*added encodes*// + static char *encode_addObject(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_int32 ParentNum); + static char *encode_addObjectExScene(vrpn_int32 &len, + const vrpn_int32 objNum); + static char *encode_objectPosition(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_float32 Pos[3]); + static char *encode_objectOrientation(vrpn_int32 &len, + const vrpn_int32 objNum, + const vrpn_float32 axis[3], + const vrpn_float32 angle); + static char *encode_objectScale(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_float32 Scale[3]); + static char *encode_removeObject(vrpn_int32 &len, const vrpn_int32 objNum); + static char *encode_clearTrimesh(vrpn_int32 &len, const vrpn_int32 objNum); + static char *encode_moveToParent(vrpn_int32 &len, const vrpn_int32 objNum, + const vrpn_int32 parentNum); + + static char *encode_setHapticOrigin(vrpn_int32 &len, + const vrpn_float32 Pos[3], + const vrpn_float32 axis[3], + const vrpn_float32 angle); + static char *encode_setSceneOrigin(vrpn_int32 &len, + const vrpn_float32 Pos[3], + const vrpn_float32 axis[3], + const vrpn_float32 angle); + static char *encode_setHapticScale(vrpn_int32 &len, + const vrpn_float32 Scale); + static char *encode_setObjectIsTouchable(vrpn_int32 &len, + const vrpn_int32 objNum, + const vrpn_bool isTouchable); + + static char *encode_forcefield(vrpn_int32 &len, + const vrpn_float32 origin[3], + const vrpn_float32 force[3], + const vrpn_float32 jacobian[3][3], + const vrpn_float32 radius); + static char *encode_error(vrpn_int32 &len, const vrpn_int32 error_code); + + // DECODING + // ajout ONDIM + static vrpn_int32 decode_custom_effect(const char *buffer, + const vrpn_int32 len, + vrpn_uint32 *effectId, + vrpn_float32 **params, + vrpn_uint32 *nbParams); + // fin ajout ONDIM + static vrpn_int32 decode_force(const char *buffer, const vrpn_int32 len, + vrpn_float64 *force); + static vrpn_int32 decode_scp(const char *buffer, const vrpn_int32 len, + vrpn_float64 *pos, vrpn_float64 *quat); + static vrpn_int32 decode_plane(const char *buffer, const vrpn_int32 len, + vrpn_float32 *plane, vrpn_float32 *kspring, + vrpn_float32 *kdamp, vrpn_float32 *fdyn, + vrpn_float32 *fstat, vrpn_int32 *plane_index, + vrpn_int32 *n_rec_cycles); + static vrpn_int32 decode_surface_effects( + const char *buffer, const vrpn_int32 len, vrpn_float32 *k_adhesion_norm, + vrpn_float32 *k_adhesion_lat, vrpn_float32 *tex_amp, + vrpn_float32 *tex_wl, vrpn_float32 *buzz_amp, vrpn_float32 *buzz_freq); + static vrpn_int32 decode_vertex(const char *buffer, const vrpn_int32 len, + vrpn_int32 *objNum, vrpn_int32 *vertNum, + vrpn_float32 *x, vrpn_float32 *y, + vrpn_float32 *z); + static vrpn_int32 decode_normal(const char *buffer, const vrpn_int32 len, + vrpn_int32 *objNum, vrpn_int32 *vertNum, + vrpn_float32 *x, vrpn_float32 *y, + vrpn_float32 *z); + static vrpn_int32 decode_triangle(const char *buffer, const vrpn_int32 len, + vrpn_int32 *objNum, vrpn_int32 *triNum, + vrpn_int32 *vert0, vrpn_int32 *vert1, + vrpn_int32 *vert2, vrpn_int32 *norm0, + vrpn_int32 *norm1, vrpn_int32 *norm2); + static vrpn_int32 decode_removeTriangle(const char *buffer, + const vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_int32 *triNum); + static vrpn_int32 + decode_updateTrimeshChanges(const char *buffer, const vrpn_int32 len, + vrpn_int32 *objNum, vrpn_float32 *kspring, + vrpn_float32 *kdamp, vrpn_float32 *fdyn, + vrpn_float32 *fstat); + static vrpn_int32 decode_setTrimeshType(const char *buffer, + const vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_int32 *type); + static vrpn_int32 decode_trimeshTransform(const char *buffer, + const vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_float32 homMatrix[16]); + + //*added decodes*// + static vrpn_int32 decode_addObject(const char *buffer, vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_int32 *ParentNum); + static vrpn_int32 decode_addObjectExScene(const char *buffer, + vrpn_int32 len, + vrpn_int32 *objNum); + static vrpn_int32 decode_objectPosition(const char *buffer, vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_float32 Pos[3]); + static vrpn_int32 decode_objectOrientation(const char *buffer, + vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_float32 axis[3], + vrpn_float32 *angle); + static vrpn_int32 decode_objectScale(const char *buffer, vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_float32 Scale[3]); + static vrpn_int32 decode_removeObject(const char *buffer, vrpn_int32 len, + vrpn_int32 *objNum); + static vrpn_int32 decode_clearTrimesh(const char *buffer, vrpn_int32 len, + vrpn_int32 *objNum); + static vrpn_int32 decode_moveToParent(const char *buffer, vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_int32 *parentNum); + + static vrpn_int32 decode_setHapticOrigin(const char *buffer, vrpn_int32 len, + vrpn_float32 Pos[3], + vrpn_float32 axis[3], + vrpn_float32 *angle); + static vrpn_int32 decode_setHapticScale(const char *buffer, vrpn_int32 len, + vrpn_float32 *Scale); + static vrpn_int32 decode_setSceneOrigin(const char *buffer, vrpn_int32 len, + vrpn_float32 Pos[3], + vrpn_float32 axis[3], + vrpn_float32 *angle); + static vrpn_int32 decode_setObjectIsTouchable(const char *buffer, + vrpn_int32 len, + vrpn_int32 *objNum, + vrpn_bool *isTouchable); + + static vrpn_int32 + decode_forcefield(const char *buffer, const vrpn_int32 len, + vrpn_float32 origin[3], vrpn_float32 force[3], + vrpn_float32 jacobian[3][3], vrpn_float32 *radius); + static vrpn_int32 decode_error(const char *buffer, const vrpn_int32 len, + vrpn_int32 *error_code); + + // constraint encoding & decoding + + static char *encode_enableConstraint(vrpn_int32 &len, vrpn_int32 enable); + static vrpn_int32 decode_enableConstraint(const char *buffer, + const vrpn_int32 len, + vrpn_int32 *enable); + + static char *encode_setConstraintMode(vrpn_int32 &len, + ConstraintGeometry mode); + static vrpn_int32 decode_setConstraintMode(const char *buffer, + const vrpn_int32 len, + ConstraintGeometry *mode); + + static char *encode_setConstraintPoint(vrpn_int32 &len, vrpn_float32 x, + vrpn_float32 y, vrpn_float32 z); + static vrpn_int32 decode_setConstraintPoint(const char *buffer, + const vrpn_int32 len, + vrpn_float32 *x, + vrpn_float32 *y, + vrpn_float32 *z); + + static char *encode_setConstraintLinePoint(vrpn_int32 &len, vrpn_float32 x, + vrpn_float32 y, vrpn_float32 z); + static vrpn_int32 decode_setConstraintLinePoint(const char *buffer, + const vrpn_int32 len, + vrpn_float32 *x, + vrpn_float32 *y, + vrpn_float32 *z); + + static char *encode_setConstraintLineDirection(vrpn_int32 &len, + vrpn_float32 x, + vrpn_float32 y, + vrpn_float32 z); + static vrpn_int32 decode_setConstraintLineDirection(const char *buffer, + const vrpn_int32 len, + vrpn_float32 *x, + vrpn_float32 *y, + vrpn_float32 *z); + + static char *encode_setConstraintPlanePoint(vrpn_int32 &len, vrpn_float32 x, + vrpn_float32 y, vrpn_float32 z); + static vrpn_int32 decode_setConstraintPlanePoint(const char *buffer, + const vrpn_int32 len, + vrpn_float32 *x, + vrpn_float32 *y, + vrpn_float32 *z); + + static char *encode_setConstraintPlaneNormal(vrpn_int32 &len, + vrpn_float32 x, vrpn_float32 y, + vrpn_float32 z); + static vrpn_int32 decode_setConstraintPlaneNormal(const char *buffer, + const vrpn_int32 len, + vrpn_float32 *x, + vrpn_float32 *y, + vrpn_float32 *z); + + static char *encode_setConstraintKSpring(vrpn_int32 &len, vrpn_float32 k); + static vrpn_int32 decode_setConstraintKSpring(const char *buffer, + const vrpn_int32 len, + vrpn_float32 *k); + + // utility functions + + static char *encodePoint(vrpn_int32 &len, vrpn_float32 x, vrpn_float32 y, + vrpn_float32 z); + static vrpn_int32 decodePoint(const char *buffer, const vrpn_int32 len, + vrpn_float32 *x, vrpn_float32 *y, + vrpn_float32 *z); + + struct timeval timestamp; + + vrpn_int32 which_plane; + + vrpn_float64 d_force[3]; + ///< d_force isn't used in vrpn_ForceDevice, but seems to be used + ///< by derived classes? What's the meaning? + + vrpn_float64 scp_pos[3]; + vrpn_float64 scp_quat[4]; // for torque + vrpn_float32 plane[4]; + + vrpn_float32 ff_origin[3]; + vrpn_float32 ff_force[3]; + vrpn_float32 ff_jacobian[3][3]; // J[i][j] = dF[i]/dx[j] + vrpn_float32 ff_radius; + + vrpn_float32 SurfaceKspring; + vrpn_float32 SurfaceKdamping; + vrpn_float32 SurfaceFstatic; + vrpn_float32 SurfaceFdynamic; + vrpn_int32 numRecCycles; + vrpn_int32 errorCode; + + vrpn_float32 SurfaceKadhesionLateral; + vrpn_float32 SurfaceKadhesionNormal; + vrpn_float32 SurfaceBuzzFreq; + vrpn_float32 SurfaceBuzzAmp; + vrpn_float32 SurfaceTextureWavelength; + vrpn_float32 SurfaceTextureAmplitude; + + // ajout ONDIM + vrpn_int32 customEffectId; + vrpn_float32 *customEffectParams; + vrpn_uint32 nbCustomEffectParams; + // fin ajout ONDIM +}; + +// User routine to handle position reports for surface contact point (SCP) +// This is in vrpn_ForceDevice rather than vrpn_Tracker because only +// a force feedback device should know anything about SCPs as this is a +// part of the force feedback model. It may be preferable to use the SCP +// rather than the tracker position for graphics so the hand position +// doesn't appear to go below the surface making the surface look very +// compliant. +typedef struct _vrpn_FORCESCPCB { + struct timeval msg_time; // Time of the report + vrpn_float64 pos[3]; // position of SCP + vrpn_float64 quat[4]; // orientation of SCP +} vrpn_FORCESCPCB; +typedef void(VRPN_CALLBACK *vrpn_FORCESCPHANDLER)(void *userdata, + const vrpn_FORCESCPCB info); + +typedef struct _vrpn_FORCECB { + struct timeval msg_time; // Time of the report + vrpn_float64 force[3]; // force value +} vrpn_FORCECB; +typedef void(VRPN_CALLBACK *vrpn_FORCECHANGEHANDLER)(void *userdata, + const vrpn_FORCECB info); + +typedef struct _vrpn_FORCEERRORCB { + struct timeval msg_time; // time of the report + vrpn_int32 error_code; // type of error +} vrpn_FORCEERRORCB; +typedef void(VRPN_CALLBACK *vrpn_FORCEERRORHANDLER)( + void *userdata, const vrpn_FORCEERRORCB info); + +class VRPN_API vrpn_ForceDevice_Remote : public vrpn_ForceDevice { +public: + // The name of the force device to connect to. + // The connection argument is used only if you already have a connection + // the device must listen on (it is not normally used). + vrpn_ForceDevice_Remote(const char *name, vrpn_Connection *cn = NULL); + virtual ~vrpn_ForceDevice_Remote(void); + + void sendSurface(void); + void startSurface(void); + void stopSurface(void); + + /** functions for a single object + * **********************************************************/ + // vertNum normNum and triNum start at 0 + void setVertex(vrpn_int32 vertNum, vrpn_float32 x, vrpn_float32 y, + vrpn_float32 z); + // NOTE: ghost doesn't take normals, + // and normals still aren't implemented for Hcollide + void setNormal(vrpn_int32 normNum, vrpn_float32 x, vrpn_float32 y, + vrpn_float32 z); + void setTriangle(vrpn_int32 triNum, vrpn_int32 vert0, vrpn_int32 vert1, + vrpn_int32 vert2, vrpn_int32 norm0 = -1, + vrpn_int32 norm1 = -1, vrpn_int32 norm2 = -1); + void removeTriangle(vrpn_int32 triNum); + // should be called to incorporate the above changes into the + // displayed trimesh + void updateTrimeshChanges(); + // set the trimesh's homogen transform matrix (in row major order) + void setTrimeshTransform(vrpn_float32 homMatrix[16]); + void clearTrimesh(void); + + /** functions for multiple objects in the haptic scene + * *************************************/ + // Add an object to the haptic scene as root (parent -1 = default) or as + // child (ParentNum =the number of the parent) + void addObject(vrpn_int32 objNum, vrpn_int32 ParentNum = -1); + // Add an object next to the haptic scene as root + void addObjectExScene(vrpn_int32 objNum); + // vertNum normNum and triNum start at 0 + void setObjectVertex(vrpn_int32 objNum, vrpn_int32 vertNum, vrpn_float32 x, + vrpn_float32 y, vrpn_float32 z); + // NOTE: ghost doesn't take normals, + // and normals still aren't implemented for Hcollide + void setObjectNormal(vrpn_int32 objNum, vrpn_int32 normNum, vrpn_float32 x, + vrpn_float32 y, vrpn_float32 z); + void setObjectTriangle(vrpn_int32 objNum, vrpn_int32 triNum, + vrpn_int32 vert0, vrpn_int32 vert1, vrpn_int32 vert2, + vrpn_int32 norm0 = -1, vrpn_int32 norm1 = -1, + vrpn_int32 norm2 = -1); + void removeObjectTriangle(vrpn_int32 objNum, vrpn_int32 triNum); + // should be called to incorporate the above changes into the + // displayed trimesh + void updateObjectTrimeshChanges(vrpn_int32 objNum); + // set the trimesh's homogen transform matrix (in row major order) + void setObjectTrimeshTransform(vrpn_int32 objNum, + vrpn_float32 homMatrix[16]); + // set position of an object + void setObjectPosition(vrpn_int32 objNum, vrpn_float32 Pos[3]); + // set orientation of an object + void setObjectOrientation(vrpn_int32 objNum, vrpn_float32 axis[3], + vrpn_float32 angle); + // set Scale of an object only x scale is supported at the moment + void setObjectScale(vrpn_int32 objNum, vrpn_float32 Scale[3]); + // remove an object from the scene + void removeObject(vrpn_int32 objNum); + void clearObjectTrimesh(vrpn_int32 objNum); + + /** Functions to organize the scene + * **********************************************************/ + // Change The parent of an object + void moveToParent(vrpn_int32 objNum, vrpn_int32 ParentNum); + // Set the Origin of the haptic device + void setHapticOrigin(vrpn_float32 Pos[3], vrpn_float32 axis[3], + vrpn_float32 angle); + // Set the scale factor of the haptic device + void setHapticScale(vrpn_float32 Scale); + // Set the Origin of the scene + void setSceneOrigin(vrpn_float32 Pos[3], vrpn_float32 axis[3], + vrpn_float32 angle); + // get new ID, use only if wish to use vrpn ids and do not want to manage + // them yourself: ids need to be unique + vrpn_int32 getNewObjectID(); + // make an object touchable or not + void setObjectIsTouchable(vrpn_int32 objNum, vrpn_bool IsTouchable = true); + + // the next time we send a trimesh we will use the following type + void useHcollide(); + void useGhost(); + + // Generalized constraint code. + // Constrains as a spring connected to a point, sliding along a line + // (constraint forces in a plane perpendicular to the line), or + // sliding along a plane (constraint forces only along the plane's + // normal). LineDirection and PlaneNormal should be normalized + // (vector length == 1). + + // Constraints are implemented as force fields, so both cannot + // run at once. + + // XXX it would be safer if changes (especially enable/disable) + // had better relaxation support + + void enableConstraint(vrpn_int32 enable); // zero disables + void setConstraintMode(ConstraintGeometry mode); + void setConstraintPoint(vrpn_float32 point[3]); + void setConstraintLinePoint(vrpn_float32 point[3]); + void setConstraintLineDirection(vrpn_float32 direction[3]); + void setConstraintPlanePoint(vrpn_float32 point[3]); + void setConstraintPlaneNormal(vrpn_float32 normal[3]); + void setConstraintKSpring(vrpn_float32 k); + + // void sendConstraint (vrpn_int32 enable, vrpn_float32 x, + // vrpn_float32 y, vrpn_float32 z, vrpn_float32 kSpr); + + // At the of the field, user feels the specified . + // As the user moves away from the origin, the force felt changes + // according to the jacobian. If the user moves further than + // from , the field cuts out. + + // XXX it would be safer for the field to attenuate rapidly + // from the value at the radius if the user moves beyond the radius + + void sendForceField(vrpn_float32 origin[3], vrpn_float32 force[3], + vrpn_float32 jacobian[3][3], vrpn_float32 radius); + void sendForceField(void); + void stopForceField(void); + + // ajout ONDIM + void startEffect(void); + void stopEffect(void); + // fin ajout ONDIM + + // This routine calls the mainloop of the connection it is on + virtual void mainloop(); + + // (un)Register a callback handler to handle a force change + // and plane equation change and trimesh change + virtual int register_force_change_handler(void *userdata, + vrpn_FORCECHANGEHANDLER handler) + { + return d_change_list.register_handler(userdata, handler); + }; + virtual int unregister_force_change_handler(void *userdata, + vrpn_FORCECHANGEHANDLER handler) + { + return d_change_list.unregister_handler(userdata, handler); + }; + + virtual int register_scp_change_handler(void *userdata, + vrpn_FORCESCPHANDLER handler) + { + return d_scp_change_list.register_handler(userdata, handler); + }; + virtual int unregister_scp_change_handler(void *userdata, + vrpn_FORCESCPHANDLER handler) + { + return d_scp_change_list.unregister_handler(userdata, handler); + }; + + virtual int register_error_handler(void *userdata, + vrpn_FORCEERRORHANDLER handler) + { + return d_error_change_list.register_handler(userdata, handler); + }; + virtual int unregister_error_handler(void *userdata, + vrpn_FORCEERRORHANDLER handler) + { + return d_error_change_list.unregister_handler(userdata, handler); + }; + +protected: + vrpn_Callback_List d_change_list; + static int VRPN_CALLBACK + handle_force_change_message(void *userdata, vrpn_HANDLERPARAM p); + + vrpn_Callback_List d_scp_change_list; + static int VRPN_CALLBACK + handle_scp_change_message(void *userdata, vrpn_HANDLERPARAM p); + + vrpn_Callback_List d_error_change_list; + static int VRPN_CALLBACK + handle_error_change_message(void *userdata, vrpn_HANDLERPARAM p); + + // constraint types + + vrpn_int32 d_conEnabled; + ConstraintGeometry d_conMode; + vrpn_float32 d_conPoint[3]; + vrpn_float32 d_conLinePoint[3]; + vrpn_float64 d_conLineDirection[3]; // (assumed) normalized + vrpn_float32 d_conPlanePoint[3]; + vrpn_float64 d_conPlaneNormal[3]; // (assumed) normalized + vrpn_float32 d_conKSpring; + + // haptic scene variables + vrpn_int32 m_NextAvailableObjectID; + + // utility functions + + void send(const char *msgbuf, vrpn_int32 len, vrpn_int32 type); +// Takes a pointer to a buffer, the length of the buffer, and the +// vrpn message type id to send. Sends the buffer reliably +// over connection AND DELETES THE BUFFER. + +#ifdef FD_SPRINGS_AS_FIELDS + + void constraintToForceField(void); +// takes the current cs_* settings and translates them into +// a force field. + +#endif // FD_SPRINGS_AS_FIELDS +}; + +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Forwarder.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Forwarder.h new file mode 100644 index 000000000000..df9f08f8bcbb --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Forwarder.h @@ -0,0 +1,132 @@ +#ifndef VRPN_FORWARDER_H +#define VRPN_FORWARDER_H + +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Connection.h" // for vrpn_Connection (ptr only), etc +#include "vrpn_Types.h" // for vrpn_int32, vrpn_uint32 + +// vrpn_Forwarder +// Tom Hudson, August 1998 +// +// Class to take messages from one VRPN connection and send them out +// on another. + +// Design decisions: +// Scale of forwarding: +// Could write a forwarder per stream (serviceName per instantiation) +// or per connection (serviceName per forward() call). Latter is +// more flexible, but takes up more memory if few distinct streams need +// to be forwarded, has a clunkier syntax, ... +// Flexibility of naming: +// We allow users to take in a message of one name and send it out +// with another name; this is useful and dangerous. + +// Faults: +// There is currently no way to specify vrpn_SENDER_ANY as a source. +// If we do, it isn't clear what sender to specify to the destination. + +class VRPN_API vrpn_ConnectionForwarder { + +public: + // Set up to forward messages from to + vrpn_ConnectionForwarder(vrpn_Connection *source, + vrpn_Connection *destination); + + ~vrpn_ConnectionForwarder(void); + + // Begins forwarding of a message type. + // Forwards messages of type and sender , + // sending them out as type from sender + // . + // Return nonzero on failure. + int forward(const char *sourceName, const char *sourceServiceName, + const char *destinationName, const char *destinationServiceName, + vrpn_uint32 classOfService = vrpn_CONNECTION_RELIABLE); + + // Stops forwarding of a message type. + // Return nonzero on failure. + int unforward(const char *sourceName, const char *sourceServiceName, + const char *destinationName, + const char *destinationServiceName, + vrpn_uint32 classOfService = vrpn_CONNECTION_RELIABLE); + +private: + static int VRPN_CALLBACK handle_message(void *, vrpn_HANDLERPARAM); + + // Translates (id, serviceId) from source to destination + // and looks up intended class of service. + // Returns nonzero if lookup fails. + vrpn_int32 map(vrpn_int32 *id, vrpn_int32 *serviceId, + vrpn_uint32 *serviceClass); + + vrpn_Connection *d_source; + vrpn_Connection *d_destination; + + struct vrpn_CONNECTIONFORWARDERRECORD { + + vrpn_CONNECTIONFORWARDERRECORD(vrpn_Connection *, vrpn_Connection *, + const char *, const char *, const char *, + const char *, vrpn_uint32); + + vrpn_int32 sourceId; // source's type id + vrpn_int32 sourceServiceId; // source's sender id + vrpn_int32 destinationId; // destination's type id + vrpn_int32 destinationServiceId; // destination's sender id + vrpn_uint32 classOfService; // class of service to send + + vrpn_CONNECTIONFORWARDERRECORD *next; + }; + + vrpn_CONNECTIONFORWARDERRECORD *d_list; +}; + +class VRPN_API vrpn_StreamForwarder { + +public: + // Set up to forward messages from sender on + // to , as if from sender + vrpn_StreamForwarder(vrpn_Connection *source, const char *sourceServiceName, + vrpn_Connection *destination, + const char *destinationServiceName); + + ~vrpn_StreamForwarder(void); + + // Begins forwarding of a message type. + // Return nonzero on failure. + int forward(const char *sourceName, const char *destinationName, + vrpn_uint32 classOfService = vrpn_CONNECTION_RELIABLE); + + // Stops forwarding of a message type. + // Return nonzero on failure. + int unforward(const char *sourceName, const char *destinationName, + vrpn_uint32 classOfService = vrpn_CONNECTION_RELIABLE); + +private: + static int VRPN_CALLBACK handle_message(void *, vrpn_HANDLERPARAM); + + // Translates (id, serviceId) from source to destination + // and looks up intended class of service. + // Returns nonzero if lookup fails. + vrpn_int32 map(vrpn_int32 *id, vrpn_uint32 *serviceClass); + + vrpn_Connection *d_source; + vrpn_int32 d_sourceService; + vrpn_Connection *d_destination; + vrpn_int32 d_destinationService; + + struct vrpn_STREAMFORWARDERRECORD { + + vrpn_STREAMFORWARDERRECORD(vrpn_Connection *, vrpn_Connection *, + const char *, const char *, vrpn_uint32); + + vrpn_int32 sourceId; // source's type id + vrpn_int32 destinationId; // destination's type id + vrpn_uint32 classOfService; // class of service to send + + vrpn_STREAMFORWARDERRECORD *next; + }; + + vrpn_STREAMFORWARDERRECORD *d_list; +}; + +#endif // VRPN_FORWARDER_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_ForwarderController.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_ForwarderController.h new file mode 100644 index 000000000000..807b49618f84 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_ForwarderController.h @@ -0,0 +1,131 @@ +#ifndef VRPN_FORWARDER_CONTROLLER_H +#define VRPN_FORWARDER_CONTROLLER_H + +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Types.h" // for vrpn_int32 +// vrpn_Forwarder_Controller +// +// Tom Hudson, September 1998 + +// Written to allow remote a client to tell a server to open another port +// and forward some messages on it to a friend of the client's. + +// Any server that wishes to implement this needs only construct a +// vrpn_Forwarder_Server for each server connection it has open and +// to call the vrpn_Forwarder_Server mainloop frequently. + +// Clients can construct a vrpn_Forwarder_Controller on a connection +// and call start_remote_forwarding(port) to tell the server to open +// , then call forward_message_type(port, name) to start forwarding +// messages of the given name. + +// This isn't an ideal solution, because it means clients need access to +// the names of the message, which they are normally insulated from. + +// Some of the fancier options of the Forwarder (renaming services or +// types, changing class of service) are hidden from the user; this +// is meant to be a simple interface and simple first implementation. + +// New Forwarder_Servers are NOT constructed on connections that a +// Forwarder_Server opens, so clients that are only listening to a +// forwarded stream cannot open new forwarders for still other clients to +// listen to. + +class VRPN_API vrpn_ConnectionForwarder; +class VRPN_API vrpn_Connection; +struct vrpn_HANDLERPARAM; + +class VRPN_API vrpn_Forwarder_Brain { + +public: + vrpn_Forwarder_Brain(vrpn_Connection *); + virtual ~vrpn_Forwarder_Brain(void); + + // Tell a Forwarder_Server to open a vrpn_Connection on remote_port. + + virtual void start_remote_forwarding(vrpn_int32 remote_port) = 0; + + // Tell a Forwarder_Server to begin forwarding messages of type + // message_type from the sender named service_name over remote_port. + + virtual void forward_message_type(vrpn_int32 remote_port, + const char *service_name, + const char *message_type) = 0; + +protected: + vrpn_Connection *d_connection; + + vrpn_int32 d_myId; + + vrpn_int32 d_start_forwarding_type; + vrpn_int32 d_forward_type; + + static char *encode_start_remote_forwarding(vrpn_int32 *length, + vrpn_int32 remote_port); + static char *encode_forward_message_type(vrpn_int32 *length, + vrpn_int32 remote_port, + const char *service_name, + const char *message_type); + + static void decode_start_remote_forwarding(const char *buffer, + vrpn_int32 *remote_port); + static void decode_forward_message_type(const char *buffer, + vrpn_int32 *remote_port, + char **service_name, + char **message_type); +}; + +// Server class + +// VRPN server builders who want to enable remotely-controlled forwarding in +// their server need only create a Forwarder_Server on their server Connections +// and call its mainloop() regularly. + +struct vrpn_Forwarder_List { + vrpn_Forwarder_List *next; + vrpn_int32 port; + vrpn_Connection *connection; + vrpn_ConnectionForwarder *forwarder; +}; + +class VRPN_API vrpn_Forwarder_Server : public vrpn_Forwarder_Brain { + +public: + vrpn_Forwarder_Server(vrpn_Connection *); + virtual ~vrpn_Forwarder_Server(void); + + virtual void mainloop(void); + + virtual void start_remote_forwarding(vrpn_int32 remote_port); + + virtual void forward_message_type(vrpn_int32 remote_port, + const char *service_name, + const char *message_type); + +protected: + vrpn_Forwarder_List *d_myForwarders; + +private: + static int VRPN_CALLBACK handle_start(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_forward(void *, vrpn_HANDLERPARAM); +}; + +// Client class + +// Construct a Forwarder_Controller on a connection to control a +// Forwarder_Server on its far end. + +class VRPN_API vrpn_Forwarder_Controller : public vrpn_Forwarder_Brain { + +public: + vrpn_Forwarder_Controller(vrpn_Connection *); + ~vrpn_Forwarder_Controller(void); + + virtual void start_remote_forwarding(vrpn_int32 remote_port); + + virtual void forward_message_type(vrpn_int32 remote_port, + const char *service_name, + const char *message_type); +}; + +#endif // VRPN_FORWARDER_CONTROLLER_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FunctionGenerator.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FunctionGenerator.h new file mode 100644 index 000000000000..21919ea38a99 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_FunctionGenerator.h @@ -0,0 +1,429 @@ +#ifndef VRPN_FUNCTIONGENERATOR_H +#define VRPN_FUNCTIONGENERATOR_H + +#include // for NULL + +#include "vrpn_Analog.h" // for vrpn_CHANNEL_MAX +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +#include "vrpn_Connection.h" +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_int32, vrpn_uint32, etc + + +const vrpn_uint32 vrpn_FUNCTION_CHANNELS_MAX = vrpn_CHANNEL_MAX; + +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_CHANNEL; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_CHANNEL_REQUEST; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_ALL_CHANNEL_REQUEST; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_SAMPLE_RATE; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_START; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_STOP; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_CHANNEL_REPLY; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_START_REPLY; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_STOP_REPLY; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_SAMPLE_RATE_REPLY; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_INTERPRETER_REQUEST; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_INTERPRETER_REPLY; +extern const char* vrpn_FUNCTION_MESSAGE_TYPE_ERROR; + +class VRPN_API vrpn_FunctionGenerator_channel; + +// a base class for all functions that vrpn_FunctionGenerator +// can generate +class VRPN_API vrpn_FunctionGenerator_function +{ +public: + virtual ~vrpn_FunctionGenerator_function() = 0; + + // concrete classes should implement this to generate the appropriate + // values for the function the class represents. nValue samples should be + // generated beginning at time startTime, and these samples should be placed + // in the provided buffer. several data members of 'channel' can modify the + // times for which values are generated. + // returns the time of the last sample generated. + virtual vrpn_float32 generateValues( vrpn_float32* buf, vrpn_uint32 nValues, + vrpn_float32 startTime, vrpn_float32 sampleRate, + vrpn_FunctionGenerator_channel* channel ) const = 0; + + // concrete classes should implement this to encode their + // function information into the specified buffer 'buf'. The + // remaining length in the buffer is stored in 'len'. At return, + // 'len' should be set to the number of characters remaining in the + // buffer and the number of characters written should be returned, + // save in case of failure, when negative should be returned. + virtual vrpn_int32 encode_to( char** buf, vrpn_int32& len ) const = 0; + + // concrete classes should implement this to decode their + // function information from the specified buffer. The remaining + // length in the buffer is stored in 'len'. At return, 'len' should + // be set to the number of characters remaining in the the buffer + // and the number of characters read should be returned, save in case + // of failure, when negative should be returned + virtual vrpn_int32 decode_from( const char** buf, vrpn_int32& len ) = 0; + + virtual vrpn_FunctionGenerator_function* clone( ) const = 0; + + // used when encoding/decoding to specify function type + enum FunctionCode + { + FUNCTION_NULL = 0, + FUNCTION_SCRIPT = 1 + }; + + // concrete classes should implement this to return the + // appropriate FunctionCode, from above + virtual FunctionCode getFunctionCode( ) const = 0; + + +}; + + +// the NULL function: generate all zeros +class VRPN_API vrpn_FunctionGenerator_function_NULL +: public virtual vrpn_FunctionGenerator_function +{ +public: + vrpn_FunctionGenerator_function_NULL( ) { } + virtual ~vrpn_FunctionGenerator_function_NULL( ) { } + + vrpn_float32 generateValues( vrpn_float32* buf, vrpn_uint32 nValues, + vrpn_float32 startTime, vrpn_float32 sampleRate, + vrpn_FunctionGenerator_channel* channel ) const; + + vrpn_int32 encode_to( char** buf, vrpn_int32& len ) const; + vrpn_int32 decode_from( const char** buf, vrpn_int32& len ); + vrpn_FunctionGenerator_function* clone( ) const; +protected: + FunctionCode getFunctionCode( ) const { return FUNCTION_NULL; } + +}; + + +class VRPN_API vrpn_FunctionGenerator_function_script +: public virtual vrpn_FunctionGenerator_function +{ +public: + vrpn_FunctionGenerator_function_script( ); + vrpn_FunctionGenerator_function_script( const char* script ); + vrpn_FunctionGenerator_function_script( const vrpn_FunctionGenerator_function_script& ); + virtual ~vrpn_FunctionGenerator_function_script(); + + virtual vrpn_float32 generateValues( vrpn_float32* buf, vrpn_uint32 nValues, + vrpn_float32 startTime, vrpn_float32 sampleRate, + vrpn_FunctionGenerator_channel* channel ) const; + + vrpn_int32 encode_to( char** buf, vrpn_int32& len ) const; + vrpn_int32 decode_from( const char** buf, vrpn_int32& len ); + vrpn_FunctionGenerator_function* clone( ) const; + + // returns a copy of the script. caller is responsible for + // calling 'delete []' to free the returned string. + char* getScript( ) const; + + const char* getConstScript( ) const + { return script; } + + vrpn_bool setScript( char* script ); + +protected: + FunctionCode getFunctionCode( ) const { return FUNCTION_SCRIPT; } + char* script; + +}; + + +class VRPN_API vrpn_FunctionGenerator_channel +{ + // note: the channel will delete its function when the function is + // no longer needed (e.g., when the channel is destroyed or the function changed) +public: + vrpn_FunctionGenerator_channel( ); + vrpn_FunctionGenerator_channel( vrpn_FunctionGenerator_function* function ); + virtual ~vrpn_FunctionGenerator_channel( ); + + const vrpn_FunctionGenerator_function* getFunction( ) const { return function; } + void setFunction( vrpn_FunctionGenerator_function* function ); + + // these return zero on success and negative on some failure. + vrpn_int32 encode_to( char** buf, vrpn_int32& len ) const; + vrpn_int32 decode_from( const char** buf, vrpn_int32& len ); + +protected: + vrpn_FunctionGenerator_function* function; + +}; + + +class VRPN_API vrpn_FunctionGenerator : public vrpn_BaseClass +{ +public: + vrpn_FunctionGenerator( const char* name, vrpn_Connection* c = NULL ); + virtual ~vrpn_FunctionGenerator( ); + + // returns the requested channel, or null if channelNum is + // greater than the maximum number of channels. + const vrpn_FunctionGenerator_channel* getChannel( vrpn_uint32 channelNum ); + + vrpn_uint32 getNumChannels( ) const { return numChannels; } + + vrpn_float32 getSampleRate( ) + { return sampleRate; } + + enum FGError + { + NO_FG_ERROR = 0, + INTERPRETER_ERROR = 1, // the interpreter (for script) had some problem + TAKING_TOO_LONG = 2, // samples were not generated quickly enough + INVALID_RESULT_QUANTITY = 3, // an incorrect number of values was generated + INVALID_RESULT_RANGE = 4 // generated values were out of range + }; + +protected: + vrpn_float32 sampleRate; // samples per second + vrpn_uint32 numChannels; + vrpn_FunctionGenerator_channel* channels[vrpn_FUNCTION_CHANNELS_MAX]; + + vrpn_int32 channelMessageID; // id for channel message (remote -> server) + vrpn_int32 requestChannelMessageID; // id for messages requesting channel info be sent (remote -> server) + vrpn_int32 requestAllChannelsMessageID; // id for messages requesting channel info of all channels be sent (remote -> server) + vrpn_int32 sampleRateMessageID; // id for message to request a sampling rate (remote -> server) + vrpn_int32 startFunctionMessageID; // id for message to start generating the function (remote -> server) + vrpn_int32 stopFunctionMessageID; // id for message to stop generating the function (remote -> server) + vrpn_int32 requestInterpreterMessageID; // id for message to request interpreter description (remote -> server) + + vrpn_int32 channelReplyMessageID; // id for reply for channel message (server -> remote) + vrpn_int32 startFunctionReplyMessageID; // id for reply to start-function message (server -> remote) + vrpn_int32 stopFunctionReplyMessageID; // id for reply to stop-function message (server -> remote) + vrpn_int32 sampleRateReplyMessageID; // id for reply to request-sample-rate message (server -> remote) + vrpn_int32 interpreterReplyMessageID; // id for reply to request-interpreter message (server -> remote) + vrpn_int32 errorMessageID; // id for error reports + + vrpn_int32 gotConnectionMessageID; // for new-connection message + + virtual int register_types( ); + + char msgbuf[vrpn_CONNECTION_TCP_BUFLEN]; + struct timeval timestamp; +}; // end class vrpn_FunctionGenerator + + +class VRPN_API vrpn_FunctionGenerator_Server : public vrpn_FunctionGenerator +{ +public: + vrpn_FunctionGenerator_Server( const char* name, vrpn_uint32 numChannels = vrpn_FUNCTION_CHANNELS_MAX, vrpn_Connection* c = NULL ); + virtual ~vrpn_FunctionGenerator_Server( ); + + virtual void mainloop( ); + + // sub-classes should implement these functions. they will be called when messages + // are received for the particular request. at the end of these functions, servers + // should call the appropriate send*Reply function, even (especially!) if the requested + // change was rejected. + virtual void setChannel( vrpn_uint32 channelNum, vrpn_FunctionGenerator_channel* channel ) = 0; + virtual void start( ) = 0; + virtual void stop( ) = 0; + virtual void setSampleRate( vrpn_float32 rate ) = 0; + + vrpn_uint32 setNumChannels( vrpn_uint32 numChannels ); + + // sub-classes should implement this function to provide a description of the type + // of interpreter used to interpret vrpn_FunctionGenerator_function_script + virtual const char* getInterpreterDescription( ) = 0; + + // sub-classes should not override these methods; these take care of + // receiving requests + static int VRPN_CALLBACK handle_channel_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_channelRequest_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_allChannelRequest_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_start_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_stop_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_sample_rate_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_interpreter_request_message( void* userdata, vrpn_HANDLERPARAM p ); + +protected: + + // sub-classes should call these functions to inform the remote side of + // changes (or of non-changes, when a requested change cannot be accepted). + // returns 0 on success and negative on failure. + int sendChannelReply( vrpn_uint32 channelNum ); + int sendSampleRateReply( ); + int sendStartReply( vrpn_bool started ); + int sendStopReply( vrpn_bool stopped ); + int sendInterpreterDescription( ); + + // sub-classes should use this function to report an error in function generation + int sendError( FGError error, vrpn_int32 channel ); + + vrpn_int32 decode_channel( const char* buf, const vrpn_int32 len, vrpn_uint32& channelNum, + vrpn_FunctionGenerator_channel& channel ); + vrpn_int32 decode_channel_request( const char* buf, const vrpn_int32 len, vrpn_uint32& channelNum ); + vrpn_int32 decode_sampleRate_request( const char* buf, const vrpn_int32 len, vrpn_float32& sampleRate ); + + vrpn_int32 encode_channel_reply( char** buf, vrpn_int32& len, const vrpn_uint32 channelNum ); + vrpn_int32 encode_start_reply( char** buf, vrpn_int32& len, const vrpn_bool isStarted ); + vrpn_int32 encode_stop_reply( char** buf, vrpn_int32& len, const vrpn_bool isStopped ); + vrpn_int32 encode_sampleRate_reply( char** buf, vrpn_int32& len, const vrpn_float32 sampleRate ); + vrpn_int32 encode_interpreterDescription_reply( char** buf, vrpn_int32& len, const char* desc ); + vrpn_int32 encode_error_report( char** buf, vrpn_int32& len, const FGError err, const vrpn_int32 channel ); + +}; // end class vrpn_FunctionGenerator_Server + + +//---------------------------------------------------------- +// ************** Users deal with the following ************* + +// User routine to handle function-generator channel replies. This +// is called when the function-generator server replies with new +// setting for some channel. +typedef struct _vrpn_FUNCTION_CHANNEL_REPLY_CB +{ + struct timeval msg_time; // Time of the report + vrpn_uint32 channelNum; // Which channel is being reported + vrpn_FunctionGenerator_channel* channel; +} vrpn_FUNCTION_CHANNEL_REPLY_CB; +typedef void (VRPN_CALLBACK *vrpn_FUNCTION_CHANGE_REPLY_HANDLER)( void *userdata, + const vrpn_FUNCTION_CHANNEL_REPLY_CB info ); + +// User routine to handle function-generator start replies. This +// is called when the function-generator server reports that it +// has started generating functions. +typedef struct _vrpn_FUNCTION_START_REPLY_CB +{ + struct timeval msg_time; // Time of the report + vrpn_bool isStarted; // did the function generation start? +} vrpn_FUNCTION_START_REPLY_CB; +typedef void (VRPN_CALLBACK *vrpn_FUNCTION_START_REPLY_HANDLER)( void *userdata, + const vrpn_FUNCTION_START_REPLY_CB info ); + +// User routine to handle function-generator stop replies. This +// is called when the function-generator server reports that it +// has stopped generating functions. +typedef struct _vrpn_FUNCTION_STOP_REPLY_CB +{ + struct timeval msg_time; // Time of the report + vrpn_bool isStopped; // did the function generation stop? +} vrpn_FUNCTION_STOP_REPLY_CB; +typedef void (VRPN_CALLBACK *vrpn_FUNCTION_STOP_REPLY_HANDLER)( void *userdata, + const vrpn_FUNCTION_STOP_REPLY_CB info ); + +// User routine to handle function-generator sample-rate replies. +// This is called when the function-generator server reports that +// the function-generation sample rate has changed. +typedef struct _vrpn_FUNCTION_SAMPLE_RATE_REPLY_CB +{ + struct timeval msg_time; // Time of the report + vrpn_float32 sampleRate; +} vrpn_FUNCTION_SAMPLE_RATE_REPLY_CB; +typedef void (VRPN_CALLBACK *vrpn_FUNCTION_SAMPLE_RATE_REPLY_HANDLER)( void *userdata, + const vrpn_FUNCTION_SAMPLE_RATE_REPLY_CB info ); + + +// User routine to handle function-generator interpreter-description replies. +// This is called when the function-generator server reports the description +// of its interpreter. +typedef struct _vrpn_FUNCTION_INTERPRETER_REPLY_CB +{ + struct timeval msg_time; // Time of the report + char* description; +} vrpn_FUNCTION_INTERPRETER_REPLY_CB; +typedef void (VRPN_CALLBACK *vrpn_FUNCTION_INTERPRETER_REPLY_HANDLER)( void *userdata, + const vrpn_FUNCTION_INTERPRETER_REPLY_CB info ); + + +// User routine to handle function-generator error notifications. +// This is called when the function-generator server reports some +// error in the generation of a function. +typedef struct _vrpn_FUNCTION_ERROR_CB +{ + struct timeval msg_time; // Time of the report + vrpn_FunctionGenerator::FGError err; + vrpn_int32 channel; +} vrpn_FUNCTION_ERROR_CB; +typedef void (VRPN_CALLBACK *vrpn_FUNCTION_ERROR_HANDLER)( void *userdata, + const vrpn_FUNCTION_ERROR_CB info ); + + +class VRPN_API vrpn_FunctionGenerator_Remote : public vrpn_FunctionGenerator +{ +public: + vrpn_FunctionGenerator_Remote( const char* name, vrpn_Connection* c = NULL ); + virtual ~vrpn_FunctionGenerator_Remote( ) { } + + int setChannel( const vrpn_uint32 channelNum, const vrpn_FunctionGenerator_channel* channel ); + int requestChannel( const vrpn_uint32 channelNum ); + int requestAllChannels( ); + int requestStart( ); + int requestStop( ); + int requestSampleRate( const vrpn_float32 rate ); + int requestInterpreterDescription( ); + + virtual void mainloop( ); + + // (un)Register a callback handler to handle a channel reply + virtual int register_channel_reply_handler( void *userdata, + vrpn_FUNCTION_CHANGE_REPLY_HANDLER handler ); + virtual int unregister_channel_reply_handler( void *userdata, + vrpn_FUNCTION_CHANGE_REPLY_HANDLER handler ); + + // (un)Register a callback handler to handle a start reply + virtual int register_start_reply_handler( void *userdata, + vrpn_FUNCTION_START_REPLY_HANDLER handler ); + virtual int unregister_start_reply_handler( void *userdata, + vrpn_FUNCTION_START_REPLY_HANDLER handler ); + + // (un)Register a callback handler to handle a stop reply + virtual int register_stop_reply_handler( void *userdata, + vrpn_FUNCTION_STOP_REPLY_HANDLER handler ); + virtual int unregister_stop_reply_handler( void *userdata, + vrpn_FUNCTION_STOP_REPLY_HANDLER handler ); + + // (un)Register a callback handler to handle a sample-rate reply + virtual int register_sample_rate_reply_handler( void *userdata, + vrpn_FUNCTION_SAMPLE_RATE_REPLY_HANDLER handler ); + virtual int unregister_sample_rate_reply_handler( void *userdata, + vrpn_FUNCTION_SAMPLE_RATE_REPLY_HANDLER handler ); + + // (un)Register a callback handler to handle an interpreter message + virtual int register_interpreter_reply_handler( void *userdata, + vrpn_FUNCTION_INTERPRETER_REPLY_HANDLER handler ); + virtual int unregister_interpreter_reply_handler( void *userdata, + vrpn_FUNCTION_INTERPRETER_REPLY_HANDLER handler ); + + virtual int register_error_handler( void* userdata, + vrpn_FUNCTION_ERROR_HANDLER handler ); + virtual int unregister_error_handler( void* userdata, + vrpn_FUNCTION_ERROR_HANDLER handler ); + + static int VRPN_CALLBACK handle_channelReply_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_startReply_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_stopReply_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_sampleRateReply_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_interpreterReply_message( void* userdata, vrpn_HANDLERPARAM p ); + static int VRPN_CALLBACK handle_error_message( void* userdata, vrpn_HANDLERPARAM p ); + +protected: + vrpn_Callback_List channel_reply_list; + vrpn_Callback_List start_reply_list; + vrpn_Callback_List stop_reply_list; + vrpn_Callback_List sample_rate_reply_list; + vrpn_Callback_List interpreter_reply_list; + vrpn_Callback_List error_list; + + + vrpn_int32 decode_channel_reply( const char* buf, const vrpn_int32 len, vrpn_uint32& channelNum ); + vrpn_int32 decode_start_reply( const char* buf, const vrpn_int32 len, vrpn_bool& isStarted ); + vrpn_int32 decode_stop_reply( const char* buf, const vrpn_int32 len, vrpn_bool& isStopped ); + vrpn_int32 decode_sampleRate_reply( const char* buf, const vrpn_int32 len ); + vrpn_int32 decode_interpreterDescription_reply( const char* buf, const vrpn_int32 len, char** desc ); + vrpn_int32 decode_error_reply( const char* buf, const vrpn_int32 len, FGError& error, vrpn_int32& channel ); + + vrpn_int32 encode_channel( char** buf, vrpn_int32& len, const vrpn_uint32 channelNum, + const vrpn_FunctionGenerator_channel* channel ); + vrpn_int32 encode_channel_request( char** buf, vrpn_int32& len, const vrpn_uint32 channelNum ); + vrpn_int32 encode_sampleRate_request( char** buf, vrpn_int32& len, const vrpn_float32 sampleRate ); + +}; // end class vrpn_FunctionGenerator_Remote + + +#endif // VRPN_FUNCTIONGENERATOR_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Imager.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Imager.h new file mode 100644 index 000000000000..8e85c6a4d798 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Imager.h @@ -0,0 +1,804 @@ +// ImagerControl (should be built into Imager, because it will always +// be the same device). The app doesn't have to use all of the +// functions if they don't want to. +// XXX Client can sent request for only subregion of image to be sent +// Server may ignore this message. +// XXX Server sets region back to total region when last connection closed. +// XXX Client can request a frame rate from the server. This is passed on +// to the server code as a handled message. Server should reset to the +// default when the last connection is closed. +// XXX Binning +// XXX integration times +// XXX Which data sets to send (nano) + +// ImagerPose (may be a separate physical device from the imager) +// XXX Lets client request new pose for imager + +// XXX When transcoding to a lower-bitcount resolution, should we +// adjust the scale and offset to make best use of the bits? Perhaps +// a local and a global scale and offset? + +#ifndef VRPN_IMAGER_H +#define VRPN_IMAGER_H +#include // for fprintf, stderr +#include // for NULL, memcpy + +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +#include "vrpn_Connection.h" +#include "vrpn_Shared.h" // for vrpn_buffer, vrpn_unbuffer, etc +#include "vrpn_Types.h" // for vrpn_uint16, vrpn_int32, etc + +const unsigned vrpn_IMAGER_MAX_CHANNELS = 100; + +/// Set of constants to tell how many points you can put into a region +/// depending on the type you are putting in there. Useful for senders +/// to know how large of a chunk they can send at once. +const unsigned vrpn_IMAGER_MAX_REGIONu8 = + (vrpn_CONNECTION_TCP_BUFLEN + - 8 * sizeof(vrpn_int16) // vrpn_Imager header size + - 6 * sizeof(vrpn_int32)) / // VRPN message header + sizeof(vrpn_uint8); +const unsigned vrpn_IMAGER_MAX_REGIONu16 = + (vrpn_CONNECTION_TCP_BUFLEN + - 8 * sizeof(vrpn_int16) // vrpn_Imager header size + - 6 * sizeof(vrpn_int32)) / // VRPN message header + sizeof(vrpn_uint16); +const unsigned vrpn_IMAGER_MAX_REGIONu12in16 = vrpn_IMAGER_MAX_REGIONu16; +const unsigned vrpn_IMAGER_MAX_REGIONf32 = + (vrpn_CONNECTION_TCP_BUFLEN + - 8 * sizeof(vrpn_int16) // vrpn_Imager header size + - 6 * sizeof(vrpn_int32)) / // VRPN message header + sizeof(vrpn_float32); + +/// Holds the description needed to convert from raw data to values for a +/// channel +class VRPN_API vrpn_Imager_Channel { + friend class vrpn_Imager_Remote; // provides access to compression status + friend class vrpn_Imager_Server; // provides access to compression status + friend class vrpn_Imager_Stream_Buffer; // provides access to + // buffer/unbuffer +public: + vrpn_Imager_Channel(void) + { + name[0] = '\0'; + units[0] = '\0'; + minVal = maxVal = 0.0; + scale = 1; + offset = 0; + d_compression = NONE; + }; + + cName name; //< Name of the data set stored in this channel + cName units; //< Units for the data set stored in this channel + vrpn_float32 minVal, + maxVal; //< Range of possible values for pixels in this channel + vrpn_float32 offset, + scale; //< Values in units are (raw_values * scale) + offset + +protected: + // The following methods are here for the derived classes and are not + // relevant + // to user code. + inline bool buffer(char **insertPt, vrpn_int32 *buflen) const + { + if (vrpn_buffer(insertPt, buflen, minVal) || + vrpn_buffer(insertPt, buflen, maxVal) || + vrpn_buffer(insertPt, buflen, offset) || + vrpn_buffer(insertPt, buflen, scale) || + vrpn_buffer(insertPt, buflen, (vrpn_uint32)d_compression) || + vrpn_buffer(insertPt, buflen, name, sizeof(name)) || + vrpn_buffer(insertPt, buflen, units, sizeof(units))) { + return false; + } + else { + return true; + } + } + + inline bool unbuffer(const char **buffer) + { + vrpn_uint32 compression; + if (vrpn_unbuffer(buffer, &minVal) || vrpn_unbuffer(buffer, &maxVal) || + vrpn_unbuffer(buffer, &offset) || vrpn_unbuffer(buffer, &scale) || + vrpn_unbuffer(buffer, &compression) || + vrpn_unbuffer(buffer, name, sizeof(name)) || + vrpn_unbuffer(buffer, units, sizeof(units))) { + return false; + } + else { + d_compression = (ChannelCompression)compression; + return true; + } + } + + typedef enum { NONE = 0 } ChannelCompression; + ChannelCompression d_compression; +}; + +/// Base class for Imager class +class VRPN_API vrpn_Imager : public vrpn_BaseClass { +public: + vrpn_Imager(const char *name, vrpn_Connection *c = NULL); + + // Data member accessors. + vrpn_int32 nRows(void) const { return d_nRows; }; + vrpn_int32 nCols(void) const { return d_nCols; }; + vrpn_int32 nDepth(void) const { return d_nDepth; }; + vrpn_int32 nChannels(void) const { return d_nChannels; }; + +protected: + vrpn_int32 d_nRows; //< Number of rows in the image + vrpn_int32 d_nCols; //< Number of columns in the image + vrpn_int32 d_nDepth; //< Number of depth stacks in the image + vrpn_int32 d_nChannels; //< Number of image data channels + vrpn_Imager_Channel d_channels[vrpn_IMAGER_MAX_CHANNELS]; + + virtual int register_types(void); + vrpn_int32 d_description_m_id; //< ID of the message type describing the + // range and channels + vrpn_int32 d_begin_frame_m_id; //< ID of the message type describing the + // start of a region + vrpn_int32 d_end_frame_m_id; //< ID of the message type describing the start + // of a region + vrpn_int32 d_discarded_frames_m_id; //< ID of the message type describing + // the discarding of one or more regions + vrpn_int32 d_throttle_frames_m_id; //< ID of the message type requesting + // throttling of sending. + vrpn_int32 d_regionu8_m_id; //< ID of the message type describing a region + // with 8-bit unsigned entries + vrpn_int32 d_regionu12in16_m_id; //< ID of the message type describing a + // region with 12-bit unsigned entries + // packed in 16 bits + vrpn_int32 d_regionu16_m_id; //< ID of the message type describing a region + // with 16-bit unsigned entries + vrpn_int32 d_regionf32_m_id; //< ID of the message type describing a region + // with 32-bit float entries +}; + +class VRPN_API vrpn_Imager_Server : public vrpn_Imager { +public: + vrpn_Imager_Server(const char *name, vrpn_Connection *c, vrpn_int32 nCols, + vrpn_int32 nRows, vrpn_int32 nDepth = 1); + + /// Add a channel to the server, returns index of the channel or -1 on + /// failure. + int add_channel(const char *name, const char *units = "unsigned8bit", + vrpn_float32 minVal = 0, vrpn_float32 maxVal = 255, + vrpn_float32 scale = 1, vrpn_float32 offset = 0); + + /// Servers must send begin/end frame pairs around contiguous sections of + /// the image + // to provide hints to the client about when to refresh displays and such. + // If they can determine when frames are missed, they should also send a + // description of missed frames, telling how many are skipped (default of + // zero means "some but don't know how many"). + bool send_begin_frame(const vrpn_uint16 cMin, const vrpn_uint16 cMax, + const vrpn_uint16 rMin, const vrpn_uint16 rMax, + const vrpn_uint16 dMin = 0, + const vrpn_uint16 dMax = 0, + const struct timeval *time = NULL); + bool send_end_frame(const vrpn_uint16 cMin, const vrpn_uint16 cMax, + const vrpn_uint16 rMin, const vrpn_uint16 rMax, + const vrpn_uint16 dMin = 0, const vrpn_uint16 dMax = 0, + const struct timeval *time = NULL); + bool send_discarded_frames(const vrpn_uint16 count = 0, + const struct timeval *time = NULL); + + /// Pack and send the region as efficiently as possible; strides are in + /// steps of the element being sent. + // These functions each take a pointer to the base of the image to be sent: + // its [0,0] element. + // If rows are being inverted, then we need to know how many rows there are + // in the total image. + bool send_region_using_base_pointer( + vrpn_int16 chanIndex, vrpn_uint16 cMin, vrpn_uint16 cMax, + vrpn_uint16 rMin, vrpn_uint16 rMax, const vrpn_uint8 *data, + vrpn_uint32 colStride, vrpn_uint32 rowStride, vrpn_uint16 nRows = 0, + bool invert_rows = false, vrpn_uint32 depthStride = 0, + vrpn_uint16 dMin = 0, vrpn_uint16 dMax = 0, + const struct timeval *time = NULL); + bool send_region_using_base_pointer( + vrpn_int16 chanIndex, vrpn_uint16 cMin, vrpn_uint16 cMax, + vrpn_uint16 rMin, vrpn_uint16 rMax, const vrpn_uint16 *data, + vrpn_uint32 colStride, vrpn_uint32 rowStride, vrpn_uint16 nRows = 0, + bool invert_rows = false, vrpn_uint32 depthStride = 0, + vrpn_uint16 dMin = 0, vrpn_uint16 dMax = 0, + const struct timeval *time = NULL); + bool send_region_using_base_pointer( + vrpn_int16 chanIndex, vrpn_uint16 cMin, vrpn_uint16 cMax, + vrpn_uint16 rMin, vrpn_uint16 rMax, const vrpn_float32 *data, + vrpn_uint32 colStride, vrpn_uint32 rowStride, vrpn_uint16 nRows = 0, + bool invert_rows = false, vrpn_uint32 depthStride = 0, + vrpn_uint16 dMin = 0, vrpn_uint16 dMax = 0, + const struct timeval *time = NULL); + + /// Pack and send the region as efficiently as possible; strides are in + /// steps of the element being sent. + // These functions each take a pointer to the first of the data values to be + // sent. This is a + // pointer to the [cMin, rMin] element of the image to be sent. Note that + // if the Y value is inverted, + // this will NOT be a pointer to the beginning of the data block, but rather + // the the beginning of + // the last line in the data block. Note that rowStride will be less than + // the number of rows in the + // whole image if the data is tightly packed into a block and the region + // does not cover all columns. + bool send_region_using_first_pointer( + vrpn_int16 chanIndex, vrpn_uint16 cMin, vrpn_uint16 cMax, + vrpn_uint16 rMin, vrpn_uint16 rMax, const vrpn_uint8 *data, + vrpn_uint32 colStride, vrpn_uint32 rowStride, vrpn_uint16 nRows = 0, + bool invert_rows = false, vrpn_uint32 depthStride = 0, + vrpn_uint16 dMin = 0, vrpn_uint16 dMax = 0, + const struct timeval *time = NULL); + bool send_region_using_first_pointer( + vrpn_int16 chanIndex, vrpn_uint16 cMin, vrpn_uint16 cMax, + vrpn_uint16 rMin, vrpn_uint16 rMax, const vrpn_uint16 *data, + vrpn_uint32 colStride, vrpn_uint32 rowStride, vrpn_uint16 nRows = 0, + bool invert_rows = false, vrpn_uint32 depthStride = 0, + vrpn_uint16 dMin = 0, vrpn_uint16 dMax = 0, + const struct timeval *time = NULL); + bool send_region_using_first_pointer( + vrpn_int16 chanIndex, vrpn_uint16 cMin, vrpn_uint16 cMax, + vrpn_uint16 rMin, vrpn_uint16 rMax, const vrpn_float32 *data, + vrpn_uint32 colStride, vrpn_uint32 rowStride, vrpn_uint16 nRows = 0, + bool invert_rows = false, vrpn_uint32 depthStride = 0, + vrpn_uint16 dMin = 0, vrpn_uint16 dMax = 0, + const struct timeval *time = NULL); + + /// Set the resolution to a different value than it had been before. + /// Returns true on success. + bool set_resolution(vrpn_int32 nCols, vrpn_int32 nRows, + vrpn_int32 nDepth = 1); + + /// Sends a description of the imager so the remote can process the region + /// messages + bool send_description(void); + + /// Handle baseclass ping/pong messages + virtual void mainloop(void); + +protected: + bool d_description_sent; //< Has the description message been sent? + vrpn_int32 d_frames_to_send; //< Set to -1 if continuous, zero or positive + // tells how many to send and then start + // dropping + vrpn_uint16 d_dropped_due_to_throttle; //< Number of frames dropped due to + // the throttle request + + // This method makes sure we send a description whenever we get a ping from + // a client object. + static int VRPN_CALLBACK + handle_ping_message(void *userdata, vrpn_HANDLERPARAM p); + + // This method handles requests to throttle the number of frames. + static int VRPN_CALLBACK + handle_throttle_message(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_last_drop_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +class VRPN_API vrpn_ImagerPose : public vrpn_BaseClass { +public: + vrpn_ImagerPose(const char *name, vrpn_Connection *c = NULL); + + /// Returns the origin of the coordinate system, + // the location of the corner of the (0,0,0) pixel. Note that + // the pixel coordinate is centered in that pixel, but that the + // pixel extends a half-pixel into the "negative" coordinates. + void get_origin(vrpn_float64 *origin) const + { + memcpy(origin, d_origin, sizeof(d_origin)); + } + + /// This is the total span of the image in columns; + // it is how far and in what direction to go from the origin + // of the image to one pixel past the pixel at the end of + // the column that (0,0,0) is in: this is the total image + // width. + void get_dCol(vrpn_float64 *dCol) const + { + memcpy(dCol, d_dCol, sizeof(d_dCol)); + } + + /// This is the total span of the image in rows; + // it is how far and in what direction to go from the origin + // of the image to one pixel past the pixel at the end of + // the row that (0,0,0) is in: this is the total image height. + void get_dRow(vrpn_float64 *dRow) const + { + memcpy(dRow, d_dRow, sizeof(d_dRow)); + } + + /// This is the total span of the image in depth; + // it is how far and in what direction to go from the origin + // of the image to one pixel past the pixel at the end of + // the depth pixel that (0,0,0) is in: this is the total + // image depth. + void get_dDepth(vrpn_float64 *dDepth) const + { + memcpy(dDepth, d_dDepth, sizeof(d_dDepth)); + } + + /// This will return the location of the center of the specified + // pixel within the image, assuming that the image covers the + // space described by this imagerpose. Note that none of the pixel + // centers will be at the end of the space, except where the image + // has no dimension (Z for a 2D image). Returns false if there is + // a problem (coordinates out of bounds). + bool compute_pixel_center(vrpn_float64 *center, const vrpn_Imager &image, + vrpn_uint16 col, vrpn_uint16 row, + vrpn_uint16 depth = 0); + +protected: + vrpn_float64 d_origin[3]; //< Origin, pixel (0,0,0) in meters + vrpn_float64 + d_dCol[3]; //< End of first columne in coordinate system in meters + vrpn_float64 d_dRow[3]; //< End of first row in coordinate system in meters + vrpn_float64 d_dDepth[3]; //< End of depth in coordinate system in meters + + virtual int register_types(void); + vrpn_int32 d_description_m_id; //< ID of the message type describing the + // range and channels +}; + +class VRPN_API vrpn_ImagerPose_Server : public vrpn_ImagerPose { +public: + vrpn_ImagerPose_Server(const char *name, const vrpn_float64 origin[3], + const vrpn_float64 dCol[3], + const vrpn_float64 dRow[3], + const vrpn_float64 *dDepth = NULL, + vrpn_Connection *c = NULL); + + /// Set the range or units. Return true on success. + bool set_range(const vrpn_float64 origin[3], const vrpn_float64 dCol[3], + const vrpn_float64 dRow[3], + const vrpn_float64 *dDepth = NULL); + + /// Sends a description of the imager so the remote can process the region + /// messages + bool send_description(void); + + /// Handle baseclass ping/pong messages + virtual void mainloop(void); + +protected: + // This method makes sure we send a description whenever we get a ping from + // a client object. + static int VRPN_CALLBACK + handle_ping_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +//------------------------------------------------------------------------------ +// Users deal with things below this line. + +//------------------------------------------------------------------------------ +// Imager_Remote is used for passing image values (pixels), converting them +// to physical units, and saying when regions are started and finished. + +const vrpn_uint16 vrpn_IMAGER_VALTYPE_UNKNOWN = 0; +const vrpn_uint16 vrpn_IMAGER_VALTYPE_UINT8 = 1; +// XXX Bad idea -- do not do this! const vrpn_uint16 +// vrpn_IMAGER_VALTYPE_UINT8RGB = 2; // Placeholder +// XXX Bad idea -- do not do this! const vrpn_uint16 +// vrpn_IMAGER_VALTYPE_UINT8BGR = 3; // Placeholder +const vrpn_uint16 vrpn_IMAGER_VALTYPE_UINT16 = 4; +const vrpn_uint16 vrpn_IMAGER_VALTYPE_UINT12IN16 = 5; +const vrpn_uint16 vrpn_IMAGER_VALTYPE_FLOAT32 = 6; + +class VRPN_API vrpn_Imager_Region; + +typedef struct _vrpn_IMAGERREGIONCB { + struct timeval msg_time; //< Timestamp of the region data's change + const vrpn_Imager_Region *region; //< New region of the image +} vrpn_IMAGERREGIONCB; + +typedef void(VRPN_CALLBACK *vrpn_IMAGERREGIONHANDLER)( + void *userdata, const vrpn_IMAGERREGIONCB info); +// There is no data in the description callback other than the time; the +// data members for the class will have been filled in, so the client should +// call nRows() and other functions to read the new values. +typedef void(VRPN_CALLBACK *vrpn_IMAGERDESCRIPTIONHANDLER)( + void *userdata, const struct timeval msg_time); + +typedef struct _vrpn_IMAGERBEGINFRAMECB { + struct timeval msg_time; //< Timestamp of the begin-frame message + vrpn_uint16 rMin; //< Minimum row in the frame + vrpn_uint16 rMax; //< Maximum row in the frame + vrpn_uint16 cMin; //< Minimum column in the frame + vrpn_uint16 cMax; //< Maximum column in the frame + vrpn_uint16 dMin; //< Minimum depth in the frame + vrpn_uint16 dMax; //< Maximum depth in the frame +} vrpn_IMAGERBEGINFRAMECB; + +typedef struct _vrpn_IMAGERENDFRAMECB { + struct timeval msg_time; //< Timestamp of the end-frame message + vrpn_uint16 rMin; //< Minimum row in the frame + vrpn_uint16 rMax; //< Maximum row in the frame + vrpn_uint16 cMin; //< Minimum column in the frame + vrpn_uint16 cMax; //< Maximum column in the frame + vrpn_uint16 dMin; //< Minimum depth in the frame + vrpn_uint16 dMax; //< Maximum depth in the frame +} vrpn_IMAGERENDFRAMECB; + +typedef struct _vrpn_IMAGERDISCARDEDFRAMESCB { + struct timeval msg_time; //< Timestamp of the begin-frame message + vrpn_uint16 count; //< Number of discarded frames (0 means "1 or more") +} vrpn_IMAGERDISCARDEDFRAMESCB; + +typedef void(VRPN_CALLBACK *vrpn_IMAGERBEGINFRAMEHANDLER)( + void *userdata, const vrpn_IMAGERBEGINFRAMECB info); +typedef void(VRPN_CALLBACK *vrpn_IMAGERENDFRAMEHANDLER)( + void *userdata, const vrpn_IMAGERENDFRAMECB info); +typedef void(VRPN_CALLBACK *vrpn_IMAGERDISCARDEDFRAMESHANDLER)( + void *userdata, const vrpn_IMAGERDISCARDEDFRAMESCB info); + +/// Helper function to convert data for a sub-region of one channel of +// the image. This is passed to the user callback handler and aids in +// getting values out of the buffer. The region is only valid during +// the actual callback handler, so users should not store pointers to +// it for later use. +class VRPN_API vrpn_Imager_Region { + friend class VRPN_API vrpn_Imager_Remote; + friend void VRPN_CALLBACK + java_vrpn_handle_region_change(void *userdata, + const vrpn_IMAGERREGIONCB info); + +public: + vrpn_Imager_Region(void) + { + d_chanIndex = -1; + d_rMin = d_rMax = d_cMin = d_cMax = 0; + d_valBuf = NULL; + d_valType = vrpn_IMAGER_VALTYPE_UNKNOWN; + d_valid = false; + } + + /// Returns the number of values in the region. + inline vrpn_uint32 getNumVals() const + { + if (!d_valid) { + return 0; + } + else { + return (d_rMax - d_rMin + 1) * (d_cMax - d_cMin + 1); + } + } + + /// Reads pixel from the region with no scale and offset applied to the + /// value. Not + /// the most efficient way to read the pixels out -- use the block read + /// routines. + inline bool read_unscaled_pixel(vrpn_uint16 c, vrpn_uint16 r, + vrpn_uint8 &val, vrpn_uint16 d = 0) const + { + if (!d_valid || (c < d_cMin) || (c > d_cMax) || (r < d_rMin) || + (r > d_rMax)) { + fprintf(stderr, "vrpn_Imager_Region::read_unscaled_pixel(): " + "Invalid region or out of range\n"); + return false; + } + else { + if (d_valType != vrpn_IMAGER_VALTYPE_UINT8) { + fprintf(stderr, "XXX " + "vrpn_Imager_Region::read_unscaled_pixel(): " + "Transcoding not implemented yet\n"); + return false; + } + else { + // The data is packed in with column varying fastest, row + // varying next, and depth + // varying slowest. Depth steps are therefore the largest + // steps. + val = + ((const vrpn_uint8 *) + d_valBuf)[(c - d_cMin) + + (d_cMax - d_cMin + 1) * + ((r - d_rMin) + + (d - d_dMin) * (d_rMax - d_rMin + 1))]; + } + } + return true; + } + + /// Reads pixel from the region with no scale and offset applied to the + /// value. Not + // the most efficient way to read the pixels out -- use the block read + // routines. + inline bool read_unscaled_pixel(vrpn_uint16 c, vrpn_uint16 r, + vrpn_uint16 &val, vrpn_uint16 d = 0) const + { + if (!d_valid || (d < d_dMin) || (d > d_dMax) || (c < d_cMin) || + (c > d_cMax) || (r < d_rMin) || (r > d_rMax)) { + fprintf(stderr, "vrpn_Imager_Region::read_unscaled_pixel(): " + "Invalid region or out of range\n"); + return false; + } + else { + if ((d_valType != vrpn_IMAGER_VALTYPE_UINT16) && + (d_valType != vrpn_IMAGER_VALTYPE_UINT12IN16)) { + fprintf(stderr, "XXX " + "vrpn_Imager_Region::read_unscaled_pixel(): " + "Transcoding not implemented yet\n"); + return false; + } + else if (vrpn_big_endian) { + fprintf(stderr, "XXX " + "vrpn_Imager_Region::read_unscaled_pixel(): " + "Not implemented on big-endian yet\n"); + return false; + } + else { + // The data is packed in with column varying fastest, row + // varying next, and depth + // varying slowest. Depth steps are therefore the largest + // steps. + val = + ((const vrpn_uint16 *) + d_valBuf)[(c - d_cMin) + + (d_cMax - d_cMin + 1) * + ((r - d_rMin) + + (d - d_dMin) * (d_rMax - d_rMin + 1))]; + } + } + return true; + } + + /// Reads pixel from the region with no scale and offset applied to the + /// value. Not + // the most efficient way to read the pixels out -- use the block read + // routines. + inline bool read_unscaled_pixel(vrpn_uint16 c, vrpn_uint16 r, + vrpn_float32 &val, vrpn_uint16 d = 0) const + { + if (!d_valid || (d < d_dMin) || (d > d_dMax) || (c < d_cMin) || + (c > d_cMax) || (r < d_rMin) || (r > d_rMax)) { + fprintf(stderr, "vrpn_Imager_Region::read_unscaled_pixel(): " + "Invalid region or out of range\n"); + return false; + } + else { + if (d_valType != vrpn_IMAGER_VALTYPE_FLOAT32) { + fprintf(stderr, "XXX " + "vrpn_Imager_Region::read_unscaled_pixel(): " + "Transcoding not implemented yet\n"); + return false; + } + else if (vrpn_big_endian) { + fprintf(stderr, "XXX " + "vrpn_Imager_Region::read_unscaled_pixel(): " + "Not implemented on big-endian yet\n"); + return false; + } + else { + // The data is packed in with column varying fastest, row + // varying next, and depth + // varying slowest. Depth steps are therefore the largest + // steps. + val = + ((const vrpn_float32 *) + d_valBuf)[(c - d_cMin) + + (d_cMax - d_cMin + 1) * + ((r - d_rMin) + + (d - d_dMin) * (d_rMax - d_rMin + 1))]; + } + } + return true; + } + + // Bulk read routines to copy the whole region right into user structures as + // efficiently as possible. + bool decode_unscaled_region_using_base_pointer( + vrpn_uint8 *data, vrpn_uint32 colStride, vrpn_uint32 rowStride, + vrpn_uint32 depthStride = 0, vrpn_uint16 nRows = 0, + bool invert_rows = false, unsigned repeat = 1) const; + // This routine also reads 12-bits-in-16-bit values. + bool decode_unscaled_region_using_base_pointer( + vrpn_uint16 *data, vrpn_uint32 colStride, vrpn_uint32 rowStride, + vrpn_uint32 depthStride = 0, vrpn_uint16 nRows = 0, + bool invert_rows = false, unsigned repeat = 1) const; + bool decode_unscaled_region_using_base_pointer( + vrpn_float32 *data, vrpn_uint32 colStride, vrpn_uint32 rowStride, + vrpn_uint32 depthStride = 0, vrpn_uint16 nRows = 0, + bool invert_rows = false, unsigned repeat = 1) const; + + // XXX Add routines to read scaled pixels. Clamp values. + + // Report the type of the values stored in the region. The above routines + // use this to decode automatically, but user code may want to do different + // things with different types of data. + vrpn_uint16 get_val_type(void) const { return d_valType; } + + vrpn_int16 d_chanIndex; //< Which channel this region holds data for + vrpn_uint16 d_rMin, d_rMax; //< Range of indices for the rows + vrpn_uint16 d_cMin, d_cMax; //< Range of indices for the columns + vrpn_uint16 d_dMin, d_dMax; //< Range of indices for the depth + +protected: + const void *d_valBuf; //< Pointer to the buffer of values + vrpn_uint16 d_valType; //< Type of the values in the buffer + bool d_valid; //< Tells whether the helper can be used. +}; + +/// This is the class users deal with: it tells the format and the region data +/// when it arrives. +class VRPN_API vrpn_Imager_Remote : public vrpn_Imager { +public: + vrpn_Imager_Remote(const char *name, vrpn_Connection *c = NULL); + + /// Register a handler for when new data arrives (can look up info in object + /// when this happens) + virtual int register_region_handler(void *userdata, + vrpn_IMAGERREGIONHANDLER handler) + { + return d_region_list.register_handler(userdata, handler); + }; + virtual int unregister_region_handler(void *userdata, + vrpn_IMAGERREGIONHANDLER handler) + { + return d_region_list.unregister_handler(userdata, handler); + } + + /// Register a handler for when the object's description changes (if + /// desired). + virtual int + register_description_handler(void *userdata, + vrpn_IMAGERDESCRIPTIONHANDLER handler) + { + return d_description_list.register_handler(userdata, handler); + }; + virtual int + unregister_description_handler(void *userdata, + vrpn_IMAGERDESCRIPTIONHANDLER handler) + { + return d_description_list.unregister_handler(userdata, handler); + } + + /// Register a handler for frame beginning (if the application cares) + virtual int + register_begin_frame_handler(void *userdata, + vrpn_IMAGERBEGINFRAMEHANDLER handler) + { + return d_begin_frame_list.register_handler(userdata, handler); + }; + virtual int + unregister_begin_frame_handler(void *userdata, + vrpn_IMAGERBEGINFRAMEHANDLER handler) + { + return d_begin_frame_list.unregister_handler(userdata, handler); + } + + /// Register a handler for frame end (if the application cares) + virtual int register_end_frame_handler(void *userdata, + vrpn_IMAGERENDFRAMEHANDLER handler) + { + return d_end_frame_list.register_handler(userdata, handler); + }; + virtual int unregister_end_frame_handler(void *userdata, + vrpn_IMAGERENDFRAMEHANDLER handler) + { + return d_end_frame_list.unregister_handler(userdata, handler); + } + + /// Register a handler for discarded frame notifications (if the application + /// cares) + virtual int + register_discarded_frames_handler(void *userdata, + vrpn_IMAGERDISCARDEDFRAMESHANDLER handler) + { + return d_discarded_frames_list.register_handler(userdata, handler); + }; + virtual int unregister_discarded_frames_handler( + void *userdata, vrpn_IMAGERDISCARDEDFRAMESHANDLER handler) + { + return d_discarded_frames_list.unregister_handler(userdata, handler); + } + + /// Request that the server send at most N more frames until a new request + /// is sent. + // This is used to throttle senders that are incurring lots of latency by + // filling + // the network with packets and blocking. The next request for "N" will add + // onto + // the request. Sending "-1" means to send continuously as fast as + // possible, + // which is the default. + virtual bool throttle_sender(vrpn_int32 N); + + /// XXX It could be nice to let the user specify separate callbacks for + // region size changed (which would be called only if the description had + // a different region size than the last time, and also the first time it + // is called) and channel changes (which would require keeping a copy of + // the old and diffing when a new description came in). Also, the interace + // could hook different callbacks for different channels IDs to let the + // Imager do the work of sorting out any mapping changes and keeping track + // of which channel is handled by which callback -- like the Tracker and its + // sensors. This should happen by name, rather than by index. It might be + // nice to provide a delete callback when a channel is removed and an add + // callback when a channel is added as well, and a change callback if the + // name, units, scale or offset change. + + /// Call this each time through the program's main loop + virtual void mainloop(void); + + /// Accessors for the member variables: can be queried in the handler for + /// object changes + const vrpn_Imager_Channel *channel(unsigned chanNum) const; + + /// have we gotten a description message yet? + bool is_description_valid() { return d_got_description; } + +protected: + bool d_got_description; //< Have we gotten a description yet? + // Lists to keep track of registered user handlers. + vrpn_Callback_List d_description_list; + vrpn_Callback_List d_region_list; + vrpn_Callback_List d_begin_frame_list; + vrpn_Callback_List d_end_frame_list; + vrpn_Callback_List d_discarded_frames_list; + + /// Handler for region update message from the server. + static int VRPN_CALLBACK + handle_region_message(void *userdata, vrpn_HANDLERPARAM p); + + /// Handler for resolution and channel list message from the server. + static int VRPN_CALLBACK + handle_description_message(void *userdata, vrpn_HANDLERPARAM p); + + /// Handler for connection dropped message + static int VRPN_CALLBACK + handle_connection_dropped_message(void *userdata, vrpn_HANDLERPARAM p); + + /// Handler for begin-frame message from the server. + static int VRPN_CALLBACK + handle_begin_frame_message(void *userdata, vrpn_HANDLERPARAM p); + + /// Handler for end-frame message from the server. + static int VRPN_CALLBACK + handle_end_frame_message(void *userdata, vrpn_HANDLERPARAM p); + + /// Handler for discarded-frames message from the server. + static int VRPN_CALLBACK + handle_discarded_frames_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +//------------------------------------------------------------------------------ +// ImagerPose_Remote deals with the physical size and location the pixels in +// an image. + +typedef void(VRPN_CALLBACK *vrpn_IMAGERPOSEDESCRIPTIONHANDLER)( + void *userdata, const struct timeval msg_time); + +class VRPN_API vrpn_ImagerPose_Remote : public vrpn_ImagerPose { +public: + vrpn_ImagerPose_Remote(const char *name, vrpn_Connection *c = NULL); + + /// Register a handler for when the object's description changes (if + /// desired) + virtual int + register_description_handler(void *userdata, + vrpn_IMAGERDESCRIPTIONHANDLER handler) + { + return d_description_list.register_handler(userdata, handler); + }; + virtual int + unregister_description_handler(void *userdata, + vrpn_IMAGERDESCRIPTIONHANDLER handler) + { + return d_description_list.unregister_handler(userdata, handler); + } + + /// Call this each time through the program's main loop + virtual void mainloop(void); + +protected: + // Lists to keep track of registered user handlers. + vrpn_Callback_List d_description_list; + + /// Handler for resolution and channel list message from the server. + static int VRPN_CALLBACK + handle_description_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_LamportClock.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_LamportClock.h new file mode 100644 index 000000000000..c168b6290e4c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_LamportClock.h @@ -0,0 +1,91 @@ +#ifndef VRPN_LAMPORT_CLOCK_H +#define VRPN_LAMPORT_CLOCK_H + +#include "vrpn_Configure.h" // for VRPN_API +#include "vrpn_Types.h" // for vrpn_uint32, vrpn_bool + +/// @class vrpn_LamportTimestamp +/// Timestamp for a single event, produced by a vrpn_LamportClock and +/// hopefully generally usable in place of a struct timeval. + +/// @class vrpn_LamportClock +/// Implements a distributed event clock as defined by Leslie Lamport in +/// some seminal papers I can't find my copies of, for use by people who +/// want to sequence events without relying on synchronization of wallclocks. + +class VRPN_API vrpn_LamportTimestamp { + + public: + + vrpn_LamportTimestamp (int vectorLength, vrpn_uint32 * vector); + vrpn_LamportTimestamp (const vrpn_LamportTimestamp &); + ~vrpn_LamportTimestamp (void); + + vrpn_LamportTimestamp & operator = (const vrpn_LamportTimestamp &); + + + // ACCESSORS + + + vrpn_bool operator < (const vrpn_LamportTimestamp & r) const; + ///< Returns vrpn_true if this timestamp precedes r. + ///< It'd be nice if we could throw an exception here, + ///< since some timestamps are incommesurate. + + + // Utility functions. + + vrpn_uint32 operator [] (int i) const; + ///< Returns the event count for the i'th host. + + int size (void) const; + ///< Returns the number of hosts participating in the timestamp. + + + private: + + void copy (const vrpn_uint32 *); + ///< Used by constructors and operator = to copy values into + ///< d_timestamp; don't we wish we were using STL? + + int d_timestampSize; + vrpn_uint32 * d_timestamp; + + vrpn_LamportTimestamp (void); + ///< UNDEFINED - not legal. + +}; + + +class VRPN_API vrpn_LamportClock { + + public: + + vrpn_LamportClock (int numHosts, int ourIndex); + ~vrpn_LamportClock (void); + + + // MANIPULATORS + + + void receive (const vrpn_LamportTimestamp &); + ///< Updates this clock to reflect a timestamp received from + ///< another clock/host. + + vrpn_LamportTimestamp * getTimestampAndAdvance (void); + ///< Increments the current timestamp and returns it. + + + private: + + int d_numHosts; + int d_ourIndex; + vrpn_uint32 * d_currentTimestamp; + +}; + + + +#endif // VRPN_LAMPORT_CLOCK_H + + diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Mutex.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Mutex.h new file mode 100644 index 000000000000..7d27a05297e0 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Mutex.h @@ -0,0 +1,333 @@ +#ifndef VRPN_MUTEX_H +#define VRPN_MUTEX_H + +#include // for NULL + +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +#include "vrpn_Types.h" // for vrpn_int32, vrpn_uint32, etc + +class VRPN_API vrpn_Connection; +struct vrpn_HANDLERPARAM; +// Every time a Mutex_Remote connects to a Mutex_Server, the server assigns +// a unique ID to the remote. +// HACK - because vrpn doesn't let us unicast within a multicast (multiple- +// connection server) (in any clean way), or identify at a MC server which +// connection a message came in over, this code is fragile - it depends +// on the fact that vrpn_Connection only allows one connection to be made +// before triggering the got_connection callback. If connections were somehow +// batched, or we multithreaded vrpn_Connection, this would break. + +class VRPN_API vrpn_Mutex { + +public: + vrpn_Mutex(const char *name, vrpn_Connection * = NULL); + virtual ~vrpn_Mutex(void) = 0; + + void mainloop(void); + +protected: + vrpn_Connection *d_connection; + + vrpn_int32 d_myId; + vrpn_int32 d_requestIndex_type; + vrpn_int32 d_requestMutex_type; + vrpn_int32 d_release_type; + vrpn_int32 d_releaseNotification_type; + vrpn_int32 d_grantRequest_type; + vrpn_int32 d_denyRequest_type; + vrpn_int32 d_initialize_type; + + void sendRequest(vrpn_int32 index); + void sendRelease(void); + void sendReleaseNotification(void); + void sendGrantRequest(vrpn_int32 index); + void sendDenyRequest(vrpn_int32 index); +}; + +class VRPN_API vrpn_Mutex_Server : public vrpn_Mutex { + +public: + vrpn_Mutex_Server(const char *name, vrpn_Connection * = NULL); + virtual ~vrpn_Mutex_Server(void); + +protected: + enum state { HELD, FREE }; + + state d_state; + + vrpn_int32 d_remoteIndex; + ///< Counts remotes who have had IDs issued to them. + + static int VRPN_CALLBACK handle_requestIndex(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_requestMutex(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_release(void *, vrpn_HANDLERPARAM); + + static int VRPN_CALLBACK handle_gotConnection(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK + handle_dropLastConnection(void *, vrpn_HANDLERPARAM); +}; + +class VRPN_API vrpn_Mutex_Remote : public vrpn_Mutex { + +public: + vrpn_Mutex_Remote(const char *name, vrpn_Connection * = NULL); + virtual ~vrpn_Mutex_Remote(void); + + // ACCESSORS + + vrpn_bool isAvailable(void) const; + ///< True from when release() is called or we receive a release + ///< message from another process until request() is called or we + ///< grant the lock to another process in response to its request + ///< message. + vrpn_bool isHeldLocally(void) const; + ///< True from when RequestGranted callbacks are triggered until + ///< release() is called. + vrpn_bool isHeldRemotely(void) const; + ///< True from when we grant the lock to another process in response + ///< to its request message until we receive a release message from + ///< another process. + + // MANIPULATORS + + void request(void); + ///< Request the distributed lock. Does not request the lock + ///< if !isAvailable(), instead automatically triggering DeniedCallbacks. + + void release(void); + ///< Release the distributed lock. Does nothing if !isHeldLocally() + ///< and there isn't a request pending. + + void addRequestGrantedCallback(void *userdata, int (*)(void *)); + ///< These callbacks are triggered when OUR request is granted. + void addRequestDeniedCallback(void *userdata, int (*)(void *)); + ///< These callbacks are triggered when OUR request is denied. + void addTakeCallback(void *userdata, int (*)(void *)); + ///< These callbacks are triggered when ANY peer gets the mutex. + void addReleaseCallback(void *userdata, int (*)(void *)); + ///< These callbacks are triggered when ANY peer releases the + ///< mutex. + +protected: + void requestIndex(void); + + enum state { OURS, REQUESTING, AVAILABLE, HELD_REMOTELY }; + + state d_state; + vrpn_int32 d_myIndex; + vrpn_bool d_requestBeforeInit; + + static int VRPN_CALLBACK handle_grantRequest(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_denyRequest(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK + handle_releaseNotification(void *, vrpn_HANDLERPARAM); + + static int VRPN_CALLBACK handle_initialize(void *, vrpn_HANDLERPARAM); + + static int VRPN_CALLBACK handle_gotConnection(void *, vrpn_HANDLERPARAM); + + void triggerGrantCallbacks(void); + void triggerDenyCallbacks(void); + void triggerTakeCallbacks(void); + void triggerReleaseCallbacks(void); + + struct mutexCallback { + int (*f)(void *); + void *userdata; + mutexCallback *next; + }; + + mutexCallback *d_reqGrantedCB; + mutexCallback *d_reqDeniedCB; + mutexCallback *d_takeCB; + mutexCallback *d_releaseCB; +}; + +/// vrpn_PeerMutex +/// +/// This class provides distributed mutual exclusion between every instance +/// with the same name for which addPeer() has been called. +/// If a process calls request() when isAvailable() returns true, +/// the mutex will attempt to secure a lock to whatever resource it is +/// governing; either RequestGranted or RequestDenied callbacks will +/// be triggered. If RequestGranted callbacks are triggered, the process +/// has the lock until it explicitly calls release() (and can verify this +/// by checking isHeldLocally()). Once the lock-owner calls release(), +/// Release callbacks at every peer will be triggered. +/// +/// Like most vrpn classes, the mainloop() must be called frequently. +/// +/// Note that none of isAvailable(), isHeldLocally(), and isHeldRemotely() +/// are true between when request() is called and either RequestGranted or +/// RequestDenied callbacks are triggered. + +// Known bugs - + +// The constructor that takes a Connection as an argument will incorrectly +// identify its IP address as the machine's default rather than the address +// used by the Connection. This should not cause any errors in the protocol, +// but will bias the tiebreaking algorithm. The same constructor will use +// the wrong port number; without this information the tiebreaking algorithm +// fails. Oops. Use only one mutex per Connection for now. + +// Possible bugs - + +// If on startup somebody else is holding the mutex we'll think it's +// available. However, if we request it they'll deny it to us and +// we won't break. +// If sites don't execute the same set of addPeer() commands, they may +// implicitly partition the network and not get true mutual exclusion. +// This could be fixed by sending an addPeer message. +// If sites execute addPeer() while the lock is held, or being requested, +// we'll break. +// - To fix: send messages, but defer all executions of addPeer until the +// lock is released. If we want to be really careful here, on getting an +// addPeer message when we think the lock is available we should request +// the lock and then (if we get it) release it immediately, without +// triggering any user callbacks. Sounds tough to code? + +// Handling more than 2 sites in a mutex requires multiconnection servers. +// It's been tested with 1-3 sites, and works fine. + +// This is an O(n^2) network traffic implementation; +// for details (and how to fix if it ever becomes a problem), +// see the implementation notes in vrpn_Mutex.C. + +class VRPN_API vrpn_PeerMutex { + +public: + vrpn_PeerMutex(const char *name, int port, const char *NICaddress = NULL); + ///< This constructor opens a new connection/port for the mutex. + + ~vrpn_PeerMutex(void); + ///< If isHeldLocally(), calls release(). + + // ACCESSORS + + vrpn_bool isAvailable(void) const; + ///< True from when release() is called or we receive a release + ///< message from another process until request() is called or we + ///< grant the lock to another process in response to its request + ///< message. + vrpn_bool isHeldLocally(void) const; + ///< True from when RequestGranted callbacks are triggered until + ///< release() is called. + vrpn_bool isHeldRemotely(void) const; + ///< True from when we grant the lock to another process in response + ///< to its request message until we receive a release message from + ///< another process. + + int numPeers(void) const; + + // MANIPULATORS + + void mainloop(void); + + void request(void); + ///< Request the distributed lock. Does not request the lock + ///< if !isAvailable(), instead automatically triggering DeniedCallbacks. + + void release(void); + ///< Release the distributed lock. Does nothing if !isHeldLocally() + ///< and there isn't a request pending. + + void addPeer(const char *stationName); + ///< Takes a VRPN station name of the form ":". + + void addRequestGrantedCallback(void *userdata, int (*)(void *)); + ///< These callbacks are triggered when OUR request is granted. + void addRequestDeniedCallback(void *userdata, int (*)(void *)); + ///< These callbacks are triggered when OUR request is denied. + void addTakeCallback(void *userdata, int (*)(void *)); + ///< These callbacks are triggered when ANY peer gets the mutex. + ///< (If several peers are competing for the mutex, and the + ///< implementation issues multiple "grants", these callbacks will + ///< only be triggered once between triggerings of ReleaseCallbacks.) + void addReleaseCallback(void *userdata, int (*)(void *)); + ///< These callbacks are triggered when ANY peer releases the + ///< mutex. + +protected: + enum state { OURS, REQUESTING, AVAILABLE, HELD_REMOTELY }; + + char *d_mutexName; + + state d_state; + + int d_numPeersGrantingLock; + ///< Counts the number of "grants" we've received after issuing + ///< a request; when this reaches d_numPeers, the lock is ours. + + vrpn_Connection *d_server; + ///< Receive on this connection. + vrpn_Connection **d_peer; + ///< Send on these connections to other Mutex's well-known-ports. + + int d_numPeers; + int d_numConnectionsAllocated; + ///< Dynamic array size for d_peer and d_peerGrantedLock. + + vrpn_uint32 d_myIP; + vrpn_uint32 d_myPort; + vrpn_uint32 d_holderIP; + vrpn_int32 d_holderPort; + + vrpn_int32 d_myId; + vrpn_int32 d_request_type; + vrpn_int32 d_release_type; + vrpn_int32 d_grantRequest_type; + vrpn_int32 d_denyRequest_type; + // vrpn_int32 d_losePeer_type; + + static int VRPN_CALLBACK handle_request(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_release(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_grantRequest(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_denyRequest(void *, vrpn_HANDLERPARAM); + + static int VRPN_CALLBACK handle_losePeer(void *, vrpn_HANDLERPARAM); + + void sendRequest(vrpn_Connection *); + void sendRelease(vrpn_Connection *); + void sendGrantRequest(vrpn_Connection *, vrpn_uint32 IPnumber, + vrpn_uint32 PortNumber); + void sendDenyRequest(vrpn_Connection *, vrpn_uint32 IPnumber, + vrpn_uint32 PortNumber); + + void triggerGrantCallbacks(void); + void triggerDenyCallbacks(void); + void triggerTakeCallbacks(void); + void triggerReleaseCallbacks(void); + + void checkGrantMutex(void); + + void init(const char *name); + + struct mutexCallback { + int (*f)(void *); + void *userdata; + mutexCallback *next; + }; + + mutexCallback *d_reqGrantedCB; + mutexCallback *d_reqDeniedCB; + mutexCallback *d_takeCB; + mutexCallback *d_releaseCB; + + struct peerData { + vrpn_uint32 IPaddress; + vrpn_uint32 port; + vrpn_bool grantedLock; + }; + + peerData *d_peerData; + ///< Needed only to clean up when a peer shuts down (mid-request). + ///< It isn't currently feasible to have all this data, so instead + ///< we abort requests that were interrupted by a shutdown. + + vrpn_PeerMutex(const char *name, vrpn_Connection *c); + ///< This constructor reuses a SERVER connection for the mutex. + ///< BUG BUG BUG - do not use this constructor; it does not reliably + ///< resolve race conditions. +}; + +#endif // VRPN_MUTEX_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Poser.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Poser.h new file mode 100644 index 000000000000..d6dd3401a419 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Poser.h @@ -0,0 +1,191 @@ +#ifndef vrpn_POSER_H +#define vrpn_POSER_H +#include // for NULL + +// NOTE: the poser class borrows heavily from the vrpn_Tracker code. +// The poser is basically the inverse of a tracker. +// We are only handling pose and velocity updates for now...acceleration +// will come later, as needed. + +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_float64, vrpn_int32 + +class VRPN_API vrpn_Connection; +struct vrpn_HANDLERPARAM; + +class VRPN_API vrpn_Poser : public vrpn_BaseClass { +public: + vrpn_Poser(const char* name, vrpn_Connection* c = NULL); + + virtual ~vrpn_Poser(void); + + void p_print(); // print the current pose + void p_print_vel(); // print the current velocity + + // a poser server should call the following to register the + // default xform and workspace request handlers + // int register_server_handlers(void); + +protected: + // client-->server + vrpn_int32 req_position_m_id; // ID of poser position message + vrpn_int32 req_position_relative_m_id; // ID of poser position delta message + vrpn_int32 req_velocity_m_id; // ID of poser velocity message + vrpn_int32 req_velocity_relative_m_id; // ID of poser velocity delta message + + // Description of current state + vrpn_float64 p_pos[3], p_quat[4]; // Current pose, (x,y,z), (qx,qy,qz,qw) + vrpn_float64 p_vel[3], + p_vel_quat[4]; // Current velocity and dQuat/vel_quat_dt + vrpn_float64 p_vel_quat_dt; // delta time (in secs) for vel_quat + struct timeval p_timestamp; // Current timestamp + + // Minimum and maximum values available for the position and velocity values + // of the poser. + vrpn_float64 p_pos_min[3], p_pos_max[3], p_pos_rot_min[3], p_pos_rot_max[3], + p_vel_min[3], p_vel_max[3], p_vel_rot_min[3], p_vel_rot_max[3]; + + virtual int register_types(void); // Called by BaseClass init() + + virtual int encode_to(char* buf); // Encodes the position + virtual int encode_vel_to(char* buf); // Encodes the velocity + + virtual void set_pose(const struct timeval t, // Sets the pose internally + const vrpn_float64 position[3], + const vrpn_float64 quaternion[4]); + virtual void set_pose_relative( + const struct timeval t, // Increments the pose internally + const vrpn_float64 + position_delta[3], // pos_new = position_delta + pos_old + const vrpn_float64 quaternion[4]); // q_new = quaternion * q_old + virtual void + set_pose_velocity(const struct timeval t, // Sets the velocity internally + const vrpn_float64 position[3], + const vrpn_float64 quaternion[4], + const vrpn_float64 interval); + virtual void set_pose_velocity_relative( + const struct timeval t, // Increments the velocity internally + const vrpn_float64 + velocity_delta[3], // vel_new = velocity_delta + vel_old + const vrpn_float64 quaternion[4], // q_new = quaternion * q_old + const vrpn_float64 + interval_delta); // interval_new = interval_delta + interval_old +}; + +//------------------------------------------------------------------------------------ +// Server Code + +/// A structure for Call-Backs related to Vrpn Poser Server +typedef struct _vrpn_POSERCB { + struct timeval msg_time; // Timestamp + /// NOTE: I think since we have different routines for handling velocity and + /// position poser requests, + /// putting poser and quaternions for both doesn't make sense. Instead, the + /// change handler should + /// take care of packing correct poser and quaternion. + vrpn_float64 pos[3]; + vrpn_float64 quat[4]; +} vrpn_POSERCB; + +typedef void(VRPN_CALLBACK* vrpn_POSERHANDLER)(void* userdata, + const vrpn_POSERCB info); + +//------------------------------------------------------------------------------------ +// Server Code +// Users supply the routines to handle requests from the client + +// This is a sample basic poser server +// + +class VRPN_API vrpn_Poser_Server : public vrpn_Poser { +public: + vrpn_Poser_Server(const char* name, vrpn_Connection* c); + + /// This function should be called each time through app mainloop. + virtual void mainloop(); + + int register_change_handler(void* userdata, vrpn_POSERHANDLER handler) + { + return d_callback_list.register_handler(userdata, handler); + }; + int unregister_change_handler(void* userdata, vrpn_POSERHANDLER handler) + { + return d_callback_list.unregister_handler(userdata, handler); + } + + int register_relative_change_handler(void* userdata, + vrpn_POSERHANDLER handler) + { + return d_relative_callback_list.register_handler(userdata, handler); + } + int unregister_relative_change_handler(void* userdata, + vrpn_POSERHANDLER handler) + { + return d_relative_callback_list.unregister_handler(userdata, handler); + } + +protected: + static int VRPN_CALLBACK + handle_change_message(void* userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_relative_change_message(void* userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_vel_change_message(void* userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_relative_vel_change_message(void* userdata, vrpn_HANDLERPARAM p); + vrpn_Callback_List d_callback_list; + vrpn_Callback_List d_relative_callback_list; +}; + +//------------------------------------------------------------------------------------ +// Client Code + +// Open a poser that is on the other end of a connection for sending updates to +// it. +class VRPN_API vrpn_Poser_Remote : public vrpn_Poser { +public: + // The name of the poser to connect to, including connection name, + // for example "poser@magnesium.cs.unc.edu". If you already + // have the connection open, you can specify it as the second parameter. + // This allows both servers and clients in the same thread, for example. + // If it is not specified, then the connection will be looked up based + // on the name passed in. + vrpn_Poser_Remote(const char* name, vrpn_Connection* c = NULL); + + // unregister all of the handlers registered with the connection + virtual ~vrpn_Poser_Remote(void); + + // This routine calls the mainloop of the connection it's on + virtual void mainloop(); + + // Routines to set the state of the poser + int request_pose(const struct timeval t, const vrpn_float64 position[3], + const vrpn_float64 quaternion[4]); + int request_pose_relative(const struct timeval t, + const vrpn_float64 position_delta[3], + const vrpn_float64 quaternion[4]); + int request_pose_velocity(const struct timeval t, + const vrpn_float64 velocity[3], + const vrpn_float64 quaternion[4], + const vrpn_float64 interval); + int request_pose_velocity_relative(const struct timeval t, + const vrpn_float64 velocity_delta[3], + const vrpn_float64 quaternion[4], + const vrpn_float64 interval_delta); + +protected: + virtual int + client_send_pose(); // Sends the current pose. Called by request_pose + virtual int client_send_pose_relative(); // Sends the current pose delta. + // Called by request_pose_relative + virtual int client_send_pose_velocity(); // Sends the current velocity. + // Called by request_pose_velocity + virtual int + client_send_pose_velocity_relative(); // Sends the current velocity delta. + // Called by + // request_pose_velocity_relative +}; + +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_RedundantTransmission.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_RedundantTransmission.h new file mode 100644 index 000000000000..9df9b86d9aab --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_RedundantTransmission.h @@ -0,0 +1,214 @@ +#ifndef VRPN_REDUNDANT_TRANSMISSION_H +#define VRPN_REDUNDANT_TRANSMISSION_H + +/// @class vrpn_RedundantTransmission +/// Helper class for vrpn_Connection that automates redundant transmission +/// for unreliable (low-latency) messages. Call pack_messages() here instead +/// of on your connection, and call mainloop() here before calling mainloop() +/// on your connection. + +#include // for NULL + +#include "vrpn_BaseClass.h" // for vrpn_BaseClass +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Connection.h" // for vrpn_Connection (ptr only), etc +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_uint32, vrpn_bool, etc + +struct timeval; + +class VRPN_API vrpn_RedundantTransmission { + +public: + vrpn_RedundantTransmission(vrpn_Connection *c); + virtual ~vrpn_RedundantTransmission(void); + + // ACCESSORS + + vrpn_uint32 defaultRetransmissions(void) const; + timeval defaultInterval(void) const; + vrpn_bool isEnabled(void) const; + + // MANIPULATORS + + virtual void mainloop(void); + ///< Determines which messages need to be resent and queues + ///< them up on the connection for transmission. + + void enable(vrpn_bool); + + virtual void setDefaults(vrpn_uint32 numRetransmissions, + timeval transmissionInterval); + ///< Set default values for future calls to pack_message(). + + virtual int pack_message(vrpn_uint32 len, timeval time, vrpn_uint32 type, + vrpn_uint32 sender, const char *buffer, + vrpn_uint32 class_of_service, + vrpn_int32 numRetransmissions = -1, + timeval *transmissionInterval = NULL); + ///< If !isEnabled(), does a normal pack_message(), but if isEnabled() + ///< ignores class_of_service and sends it vrpn_CONNECTION_LOW_LATENCY, + ///< sending it an additional number of times equal to numRetransmissions + ///< at minimum intervals of transmissionInterval. + ///< Specify -1 and NULL to use default values. + +protected: + vrpn_Connection *d_connection; + + struct queuedMessage { + vrpn_HANDLERPARAM p; + vrpn_uint32 remainingTransmissions; + timeval transmissionInterval; + timeval nextValidTime; + queuedMessage *next; + }; + + queuedMessage *d_messageList; + vrpn_uint32 d_numMessagesQueued; + ///< For debugging, mostly. + + // Default values. + + vrpn_uint32 d_numTransmissions; + timeval d_transmissionInterval; + + vrpn_bool d_isEnabled; +}; + +struct vrpn_RedundantController_Protocol { + + char *encode_set(int *len, vrpn_uint32 num, timeval interval); + void decode_set(const char **buf, vrpn_uint32 *num, timeval *interval); + + char *encode_enable(int *len, vrpn_bool); + void decode_enable(const char **buf, vrpn_bool *); + + void register_types(vrpn_Connection *); + + vrpn_int32 d_set_type; + vrpn_int32 d_enable_type; +}; + +/// @class vrpn_RedundantController +/// Accepts commands over a connection to control a local +/// vrpn_RedundantTransmission's default parameters. + +class VRPN_API vrpn_RedundantController : public vrpn_BaseClass { + +public: + vrpn_RedundantController(vrpn_RedundantTransmission *, vrpn_Connection *); + ~vrpn_RedundantController(void); + + void mainloop(void); + // Do nothing; vrpn_BaseClass requires this. + +protected: + virtual int register_types(void); + + vrpn_RedundantController_Protocol d_protocol; + + static int VRPN_CALLBACK handle_set(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_enable(void *, vrpn_HANDLERPARAM); + + vrpn_RedundantTransmission *d_object; +}; + +/// @class vrpn_RedundantRemote +/// Sends messages to a vrpn_RedundantController so that a +/// vrpn_RedundantTransmission on a server can be controlled from a client. + +class VRPN_API vrpn_RedundantRemote : public vrpn_BaseClass { + +public: + vrpn_RedundantRemote(vrpn_Connection *); + ~vrpn_RedundantRemote(void); + + void mainloop(void); + // Do nothing; vrpn_BaseClass requires this. + + void set(int numRetransmissions, timeval transmissionInterval); + void enable(vrpn_bool); + +protected: + int register_types(void); + + vrpn_RedundantController_Protocol d_protocol; +}; + +/// @class vrpn_RedundantReceiver +/// Helper class that eliminates duplicates; only the first instance of +/// a message is delivered. Registers a callback on connection for any +/// type it's told to monitor; when it gets a message back, checks its +/// list of recently-seen-timestamps for that type; if it isn't on the +/// list, it's dispatched and replaces the oldest item on the list. +/// List length is limited, so +/// if too many messages of the same type (more than VRPN_RR_LENGTH) are +/// interleaved - if transmissionInterval * numRetransmissions > +/// VRPN_RR_LENGTH * the normal rate of message generation - it will not +/// detect the redundant messages. + +// A TypeDispatcher insists on too much control of its table for +// us to use one here - we want to use the same indices as the +// vrpn_Connection we're attached to, but if we had our own TypeDispatcher +// we'd have an independent, inconsistent set of type & sender ids. + +#define VRPN_RR_LENGTH 8 + +class VRPN_API vrpn_RedundantReceiver { + +public: + vrpn_RedundantReceiver(vrpn_Connection *); + virtual ~vrpn_RedundantReceiver(void); + + virtual int register_handler(vrpn_int32 type, vrpn_MESSAGEHANDLER handler, + void *userdata, + vrpn_int32 sender = vrpn_ANY_SENDER); + virtual int unregister_handler(vrpn_int32 type, vrpn_MESSAGEHANDLER handler, + void *userdata, + vrpn_int32 sender = vrpn_ANY_SENDER); + + void record(vrpn_bool); + ///< Turns "memory" (tracking statistics of redundant reception) + ///< on and off. + + void writeMemory(const char *filename); + ///< Writes statistics to the named file: timestamp of every message + ///< received and number of copies of that message. Detects partial + ///< losses, but not when all copies are lost, since vrpn_RR doesn't + ///< expect messages. + + void clearMemory(void); + ///< Throws away / resets statistics. + +protected: + vrpn_Connection *d_connection; + + struct VRPN_API RRRecord { + RRRecord(void); + + timeval timestampSeen[VRPN_RR_LENGTH]; + int numSeen[VRPN_RR_LENGTH]; + int nextTimestampToReplace; + + vrpnMsgCallbackEntry *cb; + vrpn_bool handlerIsRegistered; + }; + + RRRecord d_records[vrpn_CONNECTION_MAX_TYPES]; + RRRecord d_generic; + + struct RRMemory { + timeval timestamp; + int numSeen; + RRMemory *next; + }; + + RRMemory *d_memory; + RRMemory *d_lastMemory; + vrpn_bool d_record; + + static int VRPN_CALLBACK + handle_possiblyRedundantMessage(void *, vrpn_HANDLERPARAM); +}; + +#endif // VRPN_REDUNDANT_TRANSMISSION_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Serial.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Serial.h new file mode 100644 index 000000000000..ac031fc92f6a --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Serial.h @@ -0,0 +1,91 @@ +#ifndef VRPN_SERIAL_H +#define VRPN_SERIAL_H + +#include "vrpn_Configure.h" // for VRPN_API +#include // For size_t + +/// @file +/// +/// @brief vrpn_Serial: Pulls all the serial port routines into one file to make +/// porting to +/// new operating systems easier. +/// +/// @author Russ Taylor, 1998 + +typedef enum { + vrpn_SER_PARITY_NONE, + vrpn_SER_PARITY_ODD, + vrpn_SER_PARITY_EVEN, + vrpn_SER_PARITY_MARK, + vrpn_SER_PARITY_SPACE +} vrpn_SER_PARITY; + +/// @brief Open a serial port, given its name and baud rate. +/// +/// Default Settings are 8 bits, no parity, 1 start and stop bits with no +/// RTS (hardware) flow control. Also, +/// set the port so that it will return immediately if there are no +/// characters or less than the number of characters requested. +/// +/// @returns the file descriptor on success,-1 on failure. +extern VRPN_API int +vrpn_open_commport(const char *portname, long baud, int charsize = 8, + vrpn_SER_PARITY parity = vrpn_SER_PARITY_NONE, + bool rts_flow = false); + +/// @name RTS Hardware Flow Control +/// Set and clear functions for the RTS ("ready to send") hardware flow- +/// control bit. These are used on a port that is already open. Some +/// devices (like the Ascension Flock of Birds) use this to reset the +/// device. Return 0 on success, nonzero on error. +/// @{ +extern VRPN_API int vrpn_set_rts(int comm); +extern VRPN_API int vrpn_clear_rts(int comm); +/// @} + +extern VRPN_API int vrpn_close_commport(int comm); + +/// @brief Throw out any characters within the input buffer. +/// @returns 0 on success, -1 on error. +extern VRPN_API int vrpn_flush_input_buffer(int comm); + +/// @brief Throw out any characters (do not send) within the output buffer +/// @returns 0 on success, tc err codes (whatever those are) on error. +extern VRPN_API int vrpn_flush_output_buffer(int comm); + +/// @brief Wait until all of the characters in the output buffer are sent, then +/// return. +/// +/// @returns 0 on success, -1 on error. +extern VRPN_API int vrpn_drain_output_buffer(int comm); + +/// @name Read routines +/// +/// Read up the the requested count of characters from the input buffer, +/// return with less if less (or none) are there. Return the number of +/// characters read, or -1 if there is an error. The second of these +/// will keep looking until the timeout period expires before returning +/// (NULL pointer will cause it to block indefinitely). +/// @{ +extern VRPN_API int +vrpn_read_available_characters(int comm, unsigned char *buffer, size_t count); +extern VRPN_API int vrpn_read_available_characters(int comm, + unsigned char *buffer, + size_t count, + struct timeval *timeout); +/// @} + +/// @name Write routines +/// +/// Write the specified number of characters. Some devices can't accept writes +/// that +/// are too fast, so need time between characters; the write_slowly function +/// handles +/// this case. +/// @} +extern VRPN_API int vrpn_write_characters(int comm, const unsigned char *buffer, + size_t bytes); +extern VRPN_API int vrpn_write_slowly(int comm, const unsigned char *buffer, + size_t bytes, int millisec_delay); + +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_SerialPort.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_SerialPort.h new file mode 100644 index 000000000000..3a41e71059b7 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_SerialPort.h @@ -0,0 +1,227 @@ +/** @file + @brief Header + + @date 2012 + + @author + Ryan Pavlik + and + http://academic.cleardefinition.com/ + Iowa State University Virtual Reality Applications Center + Human-Computer Interaction Graduate Program +*/ + +// Copyright Iowa State University 2012. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#pragma once + +// Internal Includes +#include "vrpn_Configure.h" // for VRPN_API +#include "vrpn_Serial.h" // for ::vrpn_SER_PARITY_NONE, etc + +// Library/third-party includes +// - none + +// Standard includes +#include // for runtime_error, logic_error +#include // for string + +/// @brief A simple class wrapping the functionality of vrpn_Serial.h with +/// RAII, object-orientation, and optional STL types +class VRPN_API vrpn_SerialPort { +public: + typedef int file_handle_type; + /// @brief Construct and open port + /// @sa vrpn_open_commport + /// @throws OpenFailure + vrpn_SerialPort(const char *portname, long baud, int charsize = 8, + vrpn_SER_PARITY parity = vrpn_SER_PARITY_NONE); + + /// @brief Construct without opening + vrpn_SerialPort(); + + /// @brief Destructor - closes port if open. + ~vrpn_SerialPort(); + + /// @name Open/Close Methods + /// @{ + /// @brief Open serial port + /// @sa vrpn_open_commport + /// @throws OpenFailure, AlreadyOpen + void open(const char *portname, long baud, int charsize = 8, + vrpn_SER_PARITY parity = vrpn_SER_PARITY_NONE); + bool is_open() const; + + /// @brief Close the serial port. + /// @throws NotOpen, CloseFailure + void close(); + /// @} + + /// @name Write + /// @returns number of bytes written + /// @throws WriteFailure, NotOpen + /// @{ + int write(std::string const &buffer); + int write(const unsigned char *buffer, int bytes); + /// @} + + /// @name Read + /// @throws ReadFailure, NotOpen + /// @{ + /// @brief Read available characters from input buffer, up to indicated + /// count. + int read_available_characters(unsigned char *buffer, int count); + + /// @brief Read available characters from input buffer, up to indicated + /// count (or -1 for no limit) + std::string read_available_characters(int count = -1); + + /// @brief Read available characters from input buffer, and wait up to the + /// indicated timeout for those remaining, up to indicated count. + int read_available_characters(unsigned char *buffer, int count, + struct timeval &timeout); + + /// @brief Read available characters from input buffer, and wait up to the + /// indicated timeout for those remaining, up to indicated count. + std::string read_available_characters(int count, struct timeval &timeout); + /// @} + + /// @name Buffer manipulation + /// @{ + + /// @brief Throw out any characters within the input buffer. + /// @throws FlushFailure, NotOpen + void flush_input_buffer(); + + /// @brief Throw out any characters (do not send) within the output buffer. + /// @throws FlushFailure, NotOpen + void flush_output_buffer(); + + /// @brief Wait until all of the characters in the output buffer are sent, + /// then return. + void drain_output_buffer(); + /// @} + + /// @name RTS + /// @brief Set and clear functions for the RTS ("ready to send") hardware + /// flow- control bit. + /// + /// These are used on a port that is already open. Some devices (like the + /// Ascension Flock of Birds) use this to reset the device. + /// @throws RTSFailure, NotOpen + /// @{ + void set_rts(); + void clear_rts(); + void assign_rts(bool set); + /// @} + + /// @name Serial Port Exceptions + /// @{ + struct AlreadyOpen; + struct CloseFailure; + struct DrainFailure; + struct FlushFailure; + struct NotOpen; + struct OpenFailure; + struct RTSFailure; + struct ReadFailure; + struct WriteFailure; + /// @} + +private: + void requiresOpen() const; + /// @name Non-copyable + /// @{ + vrpn_SerialPort(vrpn_SerialPort const &); + vrpn_SerialPort const &operator=(vrpn_SerialPort const &); + /// @} + file_handle_type _comm; + bool _rts_status; +}; + +struct vrpn_SerialPort::AlreadyOpen : std::logic_error { + AlreadyOpen() + : std::logic_error("Tried to open a serial port that was already open.") + { + } +}; + +struct vrpn_SerialPort::NotOpen : std::logic_error { + NotOpen() + : std::logic_error("Tried to use a serial port that was not yet open.") + { + } +}; + +struct vrpn_SerialPort::OpenFailure : std::runtime_error { + OpenFailure() + : std::runtime_error( + "Received an error when trying to open serial port.") + { + } +}; + +struct vrpn_SerialPort::CloseFailure : std::runtime_error { + CloseFailure() + : std::runtime_error( + "Received an error when trying to close serial port.") + { + } +}; + +struct vrpn_SerialPort::RTSFailure : std::runtime_error { + RTSFailure() + : std::runtime_error("Failed to modify serial port RTS status.") + { + } +}; + +struct vrpn_SerialPort::ReadFailure : std::runtime_error { + ReadFailure() + : std::runtime_error("Failure on serial port read.") + { + } +}; + +struct vrpn_SerialPort::WriteFailure : std::runtime_error { + WriteFailure() + : std::runtime_error("Failure on serial port write.") + { + } +}; + +struct vrpn_SerialPort::FlushFailure : std::runtime_error { + FlushFailure() + : std::runtime_error("Failure on serial port flush.") + { + } +}; + +struct vrpn_SerialPort::DrainFailure : std::runtime_error { + DrainFailure() + : std::runtime_error("Failure on serial port drain.") + { + } +}; + +inline bool vrpn_SerialPort::is_open() const { return _comm != -1; } + +inline void vrpn_SerialPort::assign_rts(bool set) +{ + if (set) { + set_rts(); + } + else { + clear_rts(); + } +} + +inline void vrpn_SerialPort::requiresOpen() const +{ + if (!is_open()) { + throw NotOpen(); + } +} diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Shared.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Shared.h new file mode 100644 index 000000000000..10c112b61766 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Shared.h @@ -0,0 +1,495 @@ +#pragma once + +// Horrible hack for old HPUX compiler +#ifdef hpux +#ifndef true +#define bool int +#define true 1 +#define false 0 +#endif +#endif + +#include "vrpn_Configure.h" // for VRPN_API +#include "vrpn_Types.h" // for vrpn_int32, vrpn_float64, etc +#include "vrpn_Thread.h" +#include // for memcpy() +#include // for fprintf() + +#if defined(__ANDROID__) +#include +#endif + +// IWYU pragma: no_include + +// Oct 2000: Sang-Uok changed because vrpn code was compiling but giving +// runtime errors with cygwin 1.1. I changed the code so it only uses unix +// code. I had to change includes in various files. + +// jan 2000: jeff changing the way sockets are used with cygwin. I made this +// change because I realized that we were using winsock stuff in some places, +// and cygwin stuff in others. Discovered this when our code wouldn't compile +// in cygwin-1.0 (but it did in cygwin-b20.1). + +// let's start with a clean slate +#undef VRPN_USE_WINSOCK_SOCKETS + +// Does cygwin use winsock sockets or unix sockets +//#define VRPN_CYGWIN_USES_WINSOCK_SOCKETS + +#if defined(_WIN32) && \ + (!defined(__CYGWIN__) || defined(VRPN_CYGWIN_USES_WINSOCK_SOCKETS)) +#define VRPN_USE_WINSOCK_SOCKETS +#endif + +#ifndef VRPN_USE_WINSOCK_SOCKETS +// On Win32, this constant is defined as ~0 (sockets are unsigned ints) +#define INVALID_SOCKET -1 +#define SOCKET int +#endif + +#if !(defined(_WIN32) && defined(VRPN_USE_WINSOCK_SOCKETS)) +#include // for select +#include // for htonl, htons +#endif + +#ifdef _WIN32_WCE +#define perror(x) fprintf(stderr, "%s\n", x); +#endif + +// comment from vrpn_Connection.h reads : +// +// gethostbyname() fails on SOME Windows NT boxes, but not all, +// if given an IP octet string rather than a true name. +// Until we figure out WHY, we have this extra clause in here. +// It probably wouldn't hurt to enable it for non-NT systems +// as well. +#ifdef _WIN32 +#define VRPN_USE_WINDOWS_GETHOSTBYNAME_HACK +#endif + +//-------------------------------------------------------------- +// Timeval defines. These are a bit hairy. The basic problem is +// that Windows doesn't implement gettimeofday(), nor does it +// define "struct timezone", although Winsock.h does define +// "struct timeval". The painful solution has been to define a +// vrpn_gettimeofday() function that takes a void * as a second +// argument (the timezone) and have all VRPN code call this function +// rather than gettimeofday(). On non-WINSOCK implementations, +// we alias vrpn_gettimeofday() right back to gettimeofday(), so +// that we are calling the system routine. On Windows, we will +// be using vrpn_gettimofday(). So far so good, but now user code +// would like to not have to know the difference under windows, so +// we have an optional VRPN configuration setting in vrpn_Configure.h +// that exports vrpn_gettimeofday() as gettimeofday() and also +// exports a "struct timezone" definition. Yucky, but it works and +// lets user code use the VRPN one as if it were the system call +// on Windows. + +#if (!defined(VRPN_USE_WINSOCK_SOCKETS)) +#include // for timeval, timezone, gettimeofday +// If we're using std::chrono, then we implement a new +// vrpn_gettimeofday() on top of it in a platform-independent +// manner. Otherwise, we just use the system call. +#ifndef VRPN_USE_STD_CHRONO + #define vrpn_gettimeofday gettimeofday +#endif +#else // winsock sockets + +// These are a pair of horrible hacks that instruct Windows include +// files to (1) not define min() and max() in a way that messes up +// standard-library calls to them, and (2) avoids pulling in a large +// number of Windows header files. They are not used directly within +// the VRPN library, but rather within the Windows include files to +// change the way they behave. + +#ifndef NOMINMAX +#define NOMINMAX +#endif +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif +#include +#ifndef _WIN32_WCE +#include +#endif +#ifdef VRPN_USE_WINSOCK2 +#include // struct timeval is defined here +#else +#include // struct timeval is defined here +#endif + +// Whether or not we export gettimeofday, we declare the +// vrpn_gettimeofday() function on Windows. +extern "C" VRPN_API int vrpn_gettimeofday(struct timeval *tp, + void *tzp = NULL); + +// If compiling under Cygnus Solutions Cygwin then these get defined by +// including sys/time.h. So, we will manually define only for _WIN32 +// Only do this if the Configure file has set VRPN_EXPORT_GETTIMEOFDAY, +// so that application code can get at it. All VRPN routines should be +// calling vrpn_gettimeofday() directly. + +#if defined(VRPN_EXPORT_GETTIMEOFDAY) + +// manually define this too. _WIN32 sans cygwin doesn't have gettimeofday +#define gettimeofday vrpn_gettimeofday + +#endif +#endif + +//-------------------------------------------------------------- +// vrpn_* timeval utility functions + +// IMPORTANT: timevals must be normalized to make any sense +// +// * normalized means abs(tv_usec) is less than 1,000,000 +// +// * TimevalSum and TimevalDiff do not do the right thing if +// their inputs are not normalized +// +// * TimevalScale now normalizes it's results [9/1999 it didn't before] + +// make sure tv_usec is less than 1,000,000 +extern VRPN_API struct timeval vrpn_TimevalNormalize(const struct timeval &tv); + +extern VRPN_API struct timeval vrpn_TimevalSum(const struct timeval &tv1, + const struct timeval &tv2); +extern VRPN_API struct timeval vrpn_TimevalDiff(const struct timeval &tv1, + const struct timeval &tv2); +extern VRPN_API struct timeval vrpn_TimevalScale(const struct timeval &tv, + double scale); + +/// @brief Return number of microseconds between startT and endT. +extern VRPN_API unsigned long vrpn_TimevalDuration(struct timeval endT, + struct timeval startT); + +/// @brief Return the number of seconds between startT and endT as a +/// floating-point value. +extern VRPN_API double vrpn_TimevalDurationSeconds(struct timeval endT, + struct timeval startT); + +extern VRPN_API bool vrpn_TimevalGreater(const struct timeval &tv1, + const struct timeval &tv2); +extern VRPN_API bool vrpn_TimevalEqual(const struct timeval &tv1, + const struct timeval &tv2); + +extern VRPN_API double vrpn_TimevalMsecs(const struct timeval &tv1); + +extern VRPN_API struct timeval vrpn_MsecsTimeval(const double dMsecs); +extern VRPN_API void vrpn_SleepMsecs(double dMilliSecs); + +//-------------------------------------------------------------- +// vrpn_* buffer util functions and endian-ness related +// definitions and functions. + +// xform a double to/from network order -- like htonl and htons +extern VRPN_API vrpn_float64 vrpn_htond(vrpn_float64 d); +extern VRPN_API vrpn_float64 vrpn_ntohd(vrpn_float64 d); + +// From this we get the variable "vrpn_big_endian" set to true if the machine we +// are +// on is big endian and to false if it is little endian. This can be used by +// custom packing and unpacking code to bypass the buffer and unbuffer routines +// for cases that have to be particularly fast (like video data). It is also +// used +// internally by the vrpn_htond() function. + +static const int vrpn_int_data_for_endian_test = 1; +static const char *vrpn_char_data_for_endian_test = + static_cast(static_cast((&vrpn_int_data_for_endian_test))); +static const bool vrpn_big_endian = (vrpn_char_data_for_endian_test[0] != 1); + +// Read and write strings (not single items). +extern VRPN_API int vrpn_buffer(char **insertPt, vrpn_int32 *buflen, + const char *string, vrpn_int32 length); +extern VRPN_API int vrpn_unbuffer(const char **buffer, char *string, + vrpn_int32 length); + +// Read and write timeval. +extern VRPN_API int vrpn_unbuffer(const char **buffer, timeval *t); +extern VRPN_API int vrpn_buffer(char **insertPt, vrpn_int32 *buflen, + const timeval t); + +// To read and write the atomic types defined in vrpn_Types, you use the +// templated +// buffer and unbuffer routines below. These have the same form as the ones for +// timeval, but they use types vrpn_int, vrpn_uint, vrpn_int16, vrpn_uint16, +// vrpn_int32, vrpn_uint32, vrpn_float32, and vrpn_float64. + +/** + @brief Internal header providing unbuffering facilities for a number of + types. + + @date 2011 + + @author + Ryan Pavlik + and + http://academic.cleardefinition.com/ + Iowa State University Virtual Reality Applications Center + Human-Computer Interaction Graduate Program +*/ + +// Copyright Iowa State University 2011. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +// Tested in the context of vrpn_server and vrpn_print_devices running between +// an SGI running Irix 6.5 MIPS 32-bit (big endian) and Mac OSX intel 64-bit +// (little endian) machine with a NULL tracker and it worked using the SGI +// repaired commits from 3/17/2012. + +/// @brief Contains overloaded hton() and ntoh() functions that forward +/// to their correctly-typed implementations. +namespace vrpn_byte_order { + namespace vrpn_detail { + /// Traits class to get the uint type of a given size + template struct uint_traits; + + template <> struct uint_traits<1> { + typedef vrpn_uint8 type; + }; + template <> struct uint_traits<2> { + typedef vrpn_uint16 type; + }; + template <> struct uint_traits<4> { + typedef vrpn_uint32 type; + }; + } // end of namespace vrpn_detail + + /// host to network byte order for 8-bit uints is a no-op + inline vrpn_uint8 hton(vrpn_uint8 hostval) { return hostval; } + + /// network to host byte order for 8-bit uints is a no-op + inline vrpn_uint8 ntoh(vrpn_uint8 netval) { return netval; } + + /// host to network byte order for 16-bit uints + inline vrpn_uint16 hton(vrpn_uint16 hostval) { return htons(hostval); } + + /// network to host byte order for 16-bit uints + inline vrpn_uint16 ntoh(vrpn_uint16 netval) { return ntohs(netval); } + + /// host to network byte order for 32-bit uints + inline vrpn_uint32 hton(vrpn_uint32 hostval) { return htonl(hostval); } + + /// network to host byte order for 32-bit uints + inline vrpn_uint32 ntoh(vrpn_uint32 netval) { return ntohl(netval); } + + /// host to network byte order for 64-bit floats, using vrpn_htond + inline vrpn_float64 hton(vrpn_float64 hostval) { return vrpn_htond(hostval); } + + /// network to host byte order for 64-bit floats, using vrpn_ntohd + inline vrpn_float64 ntoh(vrpn_float64 netval) { return vrpn_ntohd(netval); } + + /// Templated hton that type-puns to the same-sized uint type + /// as a fallback for those types not explicitly defined above. + template inline T hton(T input) + { + union { + T asInput; + typename vrpn_detail::uint_traits::type asInt; + } inVal, outVal; + inVal.asInput = input; + outVal.asInt = hton(inVal.asInt); + return outVal.asInput; + } + + /// Templated ntoh that type-puns to the same-sized uint type + /// as a fallback for those types not explicitly defined above. + template inline T ntoh(T input) + { + union { + T asInput; + typename vrpn_detail::uint_traits::type asInt; + } inVal, outVal; + inVal.asInput = input; + outVal.asInt = ntoh(inVal.asInt); + return outVal.asInput; + } +} // end of namespace vrpn_byte_order + +namespace vrpn_detail { + template struct remove_const { + typedef T type; + }; + + template struct remove_const { + typedef T type; + }; + + template struct vrpn_static_assert { + }; + /// @brief Each static assertion needs its message in this enum, or it will + /// always fail. + template <> struct vrpn_static_assert { + enum { SIZE_OF_BUFFER_ITEM_IS_NOT_ONE_BYTE }; + }; +} // end of namespace vrpn_detail + +#ifdef VRPN_USE_STATIC_ASSERTIONS +/// @brief Static assertion macro for limited sets of messages. +/// Inspired by http://eigen.tuxfamily.org/dox/TopicAssertions.html +#if defined(__GXX_EXPERIMENTAL_CXX0X__) || \ + (defined(_MSC_VER) && (_MSC_VER >= 1600)) +#define VRPN_STATIC_ASSERT(CONDITION, MESSAGE) \ + static_assert(CONDITION, #MESSAGE) +#else +#define VRPN_STATIC_ASSERT(CONDITION, MESSAGE) \ + (void)(::vrpn_detail::vrpn_static_assert::MESSAGE) +#endif +#else +/// Fall back to normal asserts. +#include +#define VRPN_STATIC_ASSERT(CONDITION, MESSAGE) assert((CONDITION) && #MESSAGE) +#endif + +/// Function template to unbuffer values from a buffer stored in little- +/// endian byte order. Specify the type to extract T as a template parameter. +/// The templated buffer type ByteT will be deduced automatically. +/// The input pointer will be advanced past the unbuffered value. +template +static inline T vrpn_unbuffer_from_little_endian(ByteT *&input) +{ + using namespace vrpn_byte_order; + + VRPN_STATIC_ASSERT(sizeof(ByteT) == 1, SIZE_OF_BUFFER_ITEM_IS_NOT_ONE_BYTE); + + /// Union to allow type-punning + union { + typename ::vrpn_detail::remove_const::type bytes[sizeof(T)]; + T typed; + } value; + + /// Swap known little-endian into big-endian (aka network byte order) + for (unsigned int i = 0, j = sizeof(T) - 1; i < sizeof(T); ++i, --j) { + value.bytes[i] = input[j]; + } + + /// Advance input pointer + input += sizeof(T); + + /// return value in host byte order + return ntoh(value.typed); +} + +/// Function template to unbuffer values from a buffer stored in network +/// byte order. Specify the type to extract T as a template parameter. +/// The templated buffer type ByteT will be deduced automatically. +/// The input pointer will be advanced past the unbuffered value. +template inline T vrpn_unbuffer(ByteT *&input) +{ + using namespace vrpn_byte_order; + + VRPN_STATIC_ASSERT(sizeof(ByteT) == 1, SIZE_OF_BUFFER_ITEM_IS_NOT_ONE_BYTE); + + /// Union to allow type-punning and ensure alignment + union { + typename ::vrpn_detail::remove_const::type bytes[sizeof(T)]; + T typed; + } value; + + /// Copy bytes into union + memcpy(value.bytes, input, sizeof(T)); + + /// Advance input pointer + input += sizeof(T); + + /// return value in host byte order + return ntoh(value.typed); +} + +/// Function template to buffer values to a buffer stored in little- +/// endian order. Specify the type to buffer T as a template parameter. +/// The templated buffer type ByteT will be deduced automatically. +/// The input pointer will be advanced past the unbuffered value. +template +inline int vrpn_buffer_to_little_endian(ByteT **insertPt, vrpn_int32 *buflen, const T inVal) +{ + using namespace vrpn_byte_order; + + VRPN_STATIC_ASSERT(sizeof(ByteT) == 1, SIZE_OF_BUFFER_ITEM_IS_NOT_ONE_BYTE); + + if ((insertPt == NULL) || (buflen == NULL)) { + fprintf(stderr, "vrpn_buffer: NULL pointer\n"); + return -1; + } + + if (sizeof(T) > static_cast(*buflen)) { + fprintf(stderr, "vrpn_buffer: buffer not large enough\n"); + return -1; + } + + /// Union to allow type-punning and ensure alignment + union { + typename ::vrpn_detail::remove_const::type bytes[sizeof(T)]; + T typed; + } value; + + /// Populate union in network byte order + value.typed = hton(inVal); + + /// Swap known big-endian (aka network byte order) into little-endian + for (unsigned int i = 0, j = sizeof(T) - 1; i < sizeof(T); ++i, --j) { + (*insertPt)[i] = value.bytes[j]; + } + + /// Advance insert pointer + *insertPt += sizeof(T); + /// Decrement buffer length + *buflen -= sizeof(T); + + return 0; +} + +/// Function template to buffer values to a buffer stored in network +/// byte order. Specify the type to buffer T as a template parameter. +/// The templated buffer type ByteT will be deduced automatically. +/// The input pointer will be advanced past the unbuffered value. +template +inline int vrpn_buffer(ByteT **insertPt, vrpn_int32 *buflen, const T inVal) +{ + using namespace vrpn_byte_order; + + VRPN_STATIC_ASSERT(sizeof(ByteT) == 1, SIZE_OF_BUFFER_ITEM_IS_NOT_ONE_BYTE); + + if ((insertPt == NULL) || (buflen == NULL)) { + fprintf(stderr, "vrpn_buffer: NULL pointer\n"); + return -1; + } + + if (sizeof(T) > static_cast(*buflen)) { + fprintf(stderr, "vrpn_buffer: buffer not large enough\n"); + return -1; + } + + /// Union to allow type-punning and ensure alignment + union { + typename ::vrpn_detail::remove_const::type bytes[sizeof(T)]; + T typed; + } value; + + /// Populate union in network byte order + value.typed = hton(inVal); + + /// Copy bytes into buffer + memcpy(*insertPt, value.bytes, sizeof(T)); + + /// Advance insert pointer + *insertPt += sizeof(T); + /// Decrement buffer length + *buflen -= sizeof(T); + + return 0; +} + +template +inline int vrpn_unbuffer(ByteT **input, T *lvalue) +{ + *lvalue = ::vrpn_unbuffer(*input); + return 0; +} + +// Returns true if tests work and false if they do not. +extern bool vrpn_test_pack_unpack(void); + diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_SharedObject.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_SharedObject.h new file mode 100644 index 000000000000..6dfa452d4411 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_SharedObject.h @@ -0,0 +1,572 @@ +#ifndef VRPN_SHARED_OBJECT +#define VRPN_SHARED_OBJECT + +#include // for NULL + +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +// This *must* be here to take care of winsock2.h and sys/time.h and other +// assorted system-dependent details. +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_int32, vrpn_bool, etc + +class VRPN_API vrpn_Connection; +struct timeval; +struct vrpn_HANDLERPARAM; + +class VRPN_API vrpn_LamportClock; // from "vrpn_LamportClock.h" +class VRPN_API vrpn_LamportTimestamp; + +// It's increasingly clear that we could handle all this with +// a template, except for the fact that vrpn_Shared_String is +// based on char *. All we need is a String base class. +// We could try to adopt BCString from nano's libnmb... + +// I'd like to implement shouldAcceptUpdate/shouldSendUpdate +// with the Strategy pattern (Gamma/Helm/Johnson/Vlissides 1995, pg 315). +// That would make it far, far easier to extend, but the implementation +// looks too unweildy. + +class VRPN_API vrpn_Shared_String; +class VRPN_API vrpn_Shared_float64; +class VRPN_API vrpn_Shared_int32; + +typedef int(VRPN_CALLBACK *vrpnDeferredUpdateCallback)(void *userdata); + +typedef int(VRPN_CALLBACK *vrpnSharedIntCallback)(void *userdata, + vrpn_int32 newValue, + vrpn_bool isLocal); +typedef int(VRPN_CALLBACK *vrpnSharedFloatCallback)(void *userdata, + vrpn_float64 newValue, + vrpn_bool isLocal); +typedef int(VRPN_CALLBACK *vrpnSharedStringCallback)(void *userdata, + const char *newValue, + vrpn_bool isLocal); + +typedef int(VRPN_CALLBACK *vrpnTimedSharedIntCallback)(void *userdata, + vrpn_int32 newValue, + timeval when, + vrpn_bool isLocal); +typedef int(VRPN_CALLBACK *vrpnTimedSharedFloatCallback)(void *userdata, + vrpn_float64 newValue, + timeval when, + vrpn_bool isLocal); +typedef int(VRPN_CALLBACK *vrpnTimedSharedStringCallback)(void *userdata, + const char *newValue, + timeval when, + vrpn_bool isLocal); + +// Update callbacks should return 0 on successful completion, +// nonzero on error (which will prevent further update callbacks +// from being invoked). + +typedef int(VRPN_CALLBACK *vrpnSharedIntSerializerPolicy)( + void *userdata, vrpn_int32 newValue, timeval when, + vrpn_Shared_int32 *object); +typedef int(VRPN_CALLBACK *vrpnSharedFloatSerializerPolicy)( + void *userdata, vrpn_float64 newValue, timeval when, + vrpn_Shared_float64 *object); +typedef int(VRPN_CALLBACK *vrpnSharedStringSerializerPolicy)( + void *userdata, const char *newValue, timeval when, + vrpn_Shared_String *object); + +// Policy callbacks should return 0 if the update should be accepted, +// nonzero if it should be denied. + +#define VRPN_SO_DEFAULT 0x00 +#define VRPN_SO_IGNORE_IDEMPOTENT 0x01 +#define VRPN_SO_DEFER_UPDATES 0x10 +#define VRPN_SO_IGNORE_OLD 0x100 + +// Each of these flags can be passed to all vrpn_Shared_* constructors. +// If VRPN_SO_IGNORE_IDEMPOTENT is used, calls of operator = (v) or set(v) +// are *ignored* if v == d_value. No callbacks are called, no network +// traffic takes place. +// If VRPN_SO_DEFER_UPDATES is used, calls of operator = (v) or set(v) +// on vrpn_Shared_*_Remote are sent to the server but not reflected +// locally until an update message is received from the server. +// If VRPN_SO_IGNORE_OLD is set, calls of set(v, t) are ignored if +// t < d_lastUpdate. This includes messages propagated over the network. + +// A vrpn_Shared_*_Server/Remote pair using VRPN_SO_IGNORE_OLD are +// guaranteed to reach the same final state - after quiescence (all messages +// sent on the network are delivered) they will yield the same value(), +// but they are *not* guaranteed to go through the same sequence of +// callbacks. + +// Using VRPN_SO_DEFER_UPDATES serializes all changes to d_value and +// all callbacks, so it guarantees that all instances of the shared +// variable see the same sequence of callbacks. + +// setSerializerPolicy() can be used to change the way VRPN_SO_DEFER_UPDATES +// operates. The default value described above is equivalent to calling +// setSerializerPolicy(vrpn_ACCEPT). Also possible are vrpn_DENY_REMOTE, +// which causes the serializer to ignore all updates from its peers, +// vrpn_DENY_LOCAL, which accepts updates from peers but ignores local +// updates, +// and vrpn_CALLBACK, which passes the update to a callback which can +// return zero for vrpn_ACCEPT or nonzero for vrpn_DENY. + +enum vrpn_SerializerPolicy { + vrpn_ACCEPT, + vrpn_DENY_REMOTE, + vrpn_DENY_LOCAL, + vrpn_CALLBACK +}; + +// Separated out vrpn_SharedObject from common behavior of 3 classes +// on 14 Feb 2000. Now all we need is permission to use templates to +// collapse them all together; *all* the functions remaining on the +// other classes are type-dependent and should be templatable. +// (One exception: the string that names the type. This could probably +// be cut.) + +class VRPN_API vrpn_SharedObject { + +public: + vrpn_SharedObject(const char *name, const char *tname, vrpn_int32 mode); + virtual ~vrpn_SharedObject(void); + + // ACCESSORS + + const char *name(void) const; + vrpn_bool isSerializer(void) const; + + // MANIPULATORS + + virtual void bindConnection(vrpn_Connection *); + ///< Every derived class should call this, do what it needs to, + ///< and ALSO call {server,remote}PostBindCleanup() to get + ///< myId and peerId set up and to get standard handlers registered. + + void useLamportClock(vrpn_LamportClock *); + ///< Lamport Clocks are NOT currently integrated. They should + ///< provide serialization (virtual timestamps) that work even + ///< when the clocks of the computers communicating are not + ///< roughly synchronized. + + void becomeSerializer(void); + ///< Requests that this instance of the shared object becomes + ///< the serializer (i.e. lock-arbitrator), and we can then use + ///< setSerializerPolicy to imitate a complete lock. Does nothing + ///< if we already are the serializer (isSerializer() returns true); + ///< otherwise initiates a 3-phase request protocol with the + ///< current serializer. There currently isn't any provision for + ///< notification of success (or failure). + + void registerDeferredUpdateCallback(vrpnDeferredUpdateCallback, + void *userdata); + ///< The specified function will be passed userdata when this + ///< particular shared object defers an update (receives a local + ///< update but is not the serializer and so sends the update off + ///< to the serializer). Intended to allow insertion of timing + ///< code for those times when you really want to know how long + ///< every little thing is taking. + +protected: + char *d_name; + vrpn_int32 d_mode; + timeval d_lastUpdate; + char *d_typename; // currently int32, float64, or String + + vrpn_Connection *d_connection; + // vrpn_int32 d_updateFromServer_type; + // vrpn_int32 d_updateFromRemote_type; + // vrpn_int32 d_myUpdate_type; // fragile + vrpn_int32 d_serverId; + vrpn_int32 d_remoteId; + vrpn_int32 d_myId; // fragile + vrpn_int32 d_peerId; // fragile + vrpn_int32 d_update_type; + + vrpn_int32 d_requestSerializer_type; + ///< Sent to the serializer to assume its duties. + vrpn_int32 d_grantSerializer_type; + ///< Sent by the serializer to grant a request. + vrpn_int32 d_assumeSerializer_type; + ///< Sent by a new serializer once it has been notified that + ///< its request has been granted. + + // vrpn_int32 d_updateFromServerLamport_type; + // vrpn_int32 d_updateFromRemoteLamport_type; + vrpn_int32 d_lamportUpdate_type; + + vrpn_bool d_isSerializer; + ///< default to vrpn_TRUE for servers, FALSE for remotes + vrpn_bool d_isNegotiatingSerializer; + ///< As long as we have inorder delivery, this should be + ///< sufficient to keep us from getting many at once. + + virtual vrpn_bool shouldSendUpdate(vrpn_bool isLocalSet, + vrpn_bool acceptedUpdate); + + int yankCallbacks(vrpn_bool isLocal); + ///< must set d_lastUpdate BEFORE calling yankCallbacks() + + static int VRPN_CALLBACK + handle_requestSerializer(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_grantSerializer(void *, vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_assumeSerializer(void *, vrpn_HANDLERPARAM); + + vrpn_bool d_queueSets; + ///< If this is true, no set()s are processed; instead, they + ///< are queued for later execution. + ///< NOT IMPLEMENTED + + vrpn_LamportClock *d_lClock; + vrpn_LamportTimestamp *d_lastLamportUpdate; + + struct deferredUpdateCallbackEntry { + vrpnDeferredUpdateCallback handler; + void *userdata; + deferredUpdateCallbackEntry *next; + }; + deferredUpdateCallbackEntry *d_deferredUpdateCallbacks; + + int yankDeferredUpdateCallbacks(void); + ///< returns -1 on error (i.e. nonzero return by a callback) + + void serverPostBindCleanup(void); + void remotePostBindCleanup(void); + + virtual void sendUpdate(void) = 0; + ///< Should invoke default sendUpdate() for this derived type. + virtual int handleUpdate(vrpn_HANDLERPARAM) = 0; + + static int VRPN_CALLBACK handle_gotConnection(void *, vrpn_HANDLERPARAM); + ///< Register this handler in postBindCleanup(); + ///< it calls sendUpdate() to make sure the remote has the + ///< correct value on first connection. + static int VRPN_CALLBACK handle_update(void *, vrpn_HANDLERPARAM); + ///< Passes arguments to handleUpdate() for this type; + ///< registered in postBindCleanup(); + +private: + void postBindCleanup(void); +}; + +class VRPN_API vrpn_Shared_int32 : public vrpn_SharedObject { + +public: + vrpn_Shared_int32(const char *name, vrpn_int32 defaultValue = 0, + vrpn_int32 mode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_int32(void); + + // ACCESSORS + + vrpn_int32 value(void) const; + operator vrpn_int32() const; + + // MANIPULATORS + + vrpn_Shared_int32 &operator=(vrpn_int32 newValue); + // calls set(newValue, now); + + vrpn_Shared_int32 &set(vrpn_int32 newValue, timeval when); + // calls protected set (newValue, when, vrpn_TRUE); + + void register_handler(vrpnSharedIntCallback, void *); + void unregister_handler(vrpnSharedIntCallback, void *); + void register_handler(vrpnTimedSharedIntCallback, void *); + void unregister_handler(vrpnTimedSharedIntCallback, void *); + // Callbacks are (currently) called *AFTER* the assignment + // has been made, so any check of the value of their shared int + // will return newValue + + void setSerializerPolicy(vrpn_SerializerPolicy policy = vrpn_ACCEPT, + vrpnSharedIntSerializerPolicy f = NULL, + void *userdata = NULL); + +protected: + vrpn_int32 d_value; + + // callback code + // Could generalize this by making a class that gets passed + // a vrpn_HANDLERPARAM and passes whatever is needed to its callback, + // but it's not worth doing that unless we need a third or fourth + // kind of callback. + struct callbackEntry { + vrpnSharedIntCallback handler; + void *userdata; + callbackEntry *next; + }; + callbackEntry *d_callbacks; + struct timedCallbackEntry { + vrpnTimedSharedIntCallback handler; + void *userdata; + timedCallbackEntry *next; + }; + timedCallbackEntry *d_timedCallbacks; + + vrpn_Shared_int32 &set(vrpn_int32, timeval, vrpn_bool isLocalSet, + vrpn_LamportTimestamp * = NULL); + + virtual vrpn_bool shouldAcceptUpdate(vrpn_int32 newValue, timeval when, + vrpn_bool isLocalSet, + vrpn_LamportTimestamp *); + + virtual void sendUpdate(void); + void sendUpdate(vrpn_int32 newValue, timeval when); + + void encode(char **buffer, vrpn_int32 *len, vrpn_int32 newValue, + timeval when) const; + void encodeLamport(char **buffer, vrpn_int32 *len, vrpn_int32 newValue, + timeval when, vrpn_LamportTimestamp *t) const; + // We used to have sendUpdate() and encode() just read off of + // d_value and d_lastUpdate, but that doesn't work when we're + // serializing (VRPN_SO_DEFER_UPDATES), because we don't want + // to change the local values but do want to send the new values + // to the serializer. + void decode(const char **buffer, vrpn_int32 *len, vrpn_int32 *newValue, + timeval *when) const; + void decodeLamport(const char **buffer, vrpn_int32 *len, + vrpn_int32 *newValue, timeval *when, + vrpn_LamportTimestamp **t) const; + + int yankCallbacks(vrpn_bool isLocal); + // must set d_lastUpdate BEFORE calling yankCallbacks() + + // serializer policy code + vrpn_SerializerPolicy d_policy; // default to vrpn_ACCEPT + vrpnSharedIntSerializerPolicy d_policyCallback; + void *d_policyUserdata; + + int handleUpdate(vrpn_HANDLERPARAM); + + static int VRPN_CALLBACK handle_lamportUpdate(void *, vrpn_HANDLERPARAM); +}; + +// I don't think the derived classes should have to have operator = () +// defined (they didn't in the last version??), but both SGI and HP +// compilers seem to insist on it. + +class VRPN_API vrpn_Shared_int32_Server : public vrpn_Shared_int32 { + +public: + vrpn_Shared_int32_Server(const char *name, vrpn_int32 defaultValue = 0, + vrpn_int32 defaultMode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_int32_Server(void); + + vrpn_Shared_int32_Server &operator=(vrpn_int32 newValue); + + virtual void bindConnection(vrpn_Connection *); + +protected: +}; + +class VRPN_API vrpn_Shared_int32_Remote : public vrpn_Shared_int32 { + +public: + vrpn_Shared_int32_Remote(const char *name, vrpn_int32 defaultValue = 0, + vrpn_int32 defaultMode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_int32_Remote(void); + + vrpn_Shared_int32_Remote &operator=(vrpn_int32 newValue); + + virtual void bindConnection(vrpn_Connection *); +}; + +class VRPN_API vrpn_Shared_float64 : public vrpn_SharedObject { + +public: + vrpn_Shared_float64(const char *name, vrpn_float64 defaultValue = 0.0, + vrpn_int32 mode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_float64(void); + + // ACCESSORS + + vrpn_float64 value(void) const; + operator vrpn_float64() const; + + // MANIPULATORS + + vrpn_Shared_float64 &operator=(vrpn_float64 newValue); + // calls set(newValue, now); + + virtual vrpn_Shared_float64 &set(vrpn_float64 newValue, timeval when); + // calls protected set (newValue, when, vrpn_TRUE); + + void register_handler(vrpnSharedFloatCallback, void *); + void unregister_handler(vrpnSharedFloatCallback, void *); + void register_handler(vrpnTimedSharedFloatCallback, void *); + void unregister_handler(vrpnTimedSharedFloatCallback, void *); + // Callbacks are (currently) called *AFTER* the assignment + // has been made, so any check of the value of their shared int + // will return newValue + + void setSerializerPolicy(vrpn_SerializerPolicy policy = vrpn_ACCEPT, + vrpnSharedFloatSerializerPolicy f = NULL, + void *userdata = NULL); + +protected: + vrpn_float64 d_value; + + // callback code + // Could generalize this by making a class that gets passed + // a vrpn_HANDLERPARAM and passes whatever is needed to its callback, + // but it's not worth doing that unless we need a third or fourth + // kind of callback. + struct callbackEntry { + vrpnSharedFloatCallback handler; + void *userdata; + callbackEntry *next; + }; + callbackEntry *d_callbacks; + struct timedCallbackEntry { + vrpnTimedSharedFloatCallback handler; + void *userdata; + timedCallbackEntry *next; + }; + timedCallbackEntry *d_timedCallbacks; + + vrpn_SerializerPolicy d_policy; // default to vrpn_ACCEPT + vrpnSharedFloatSerializerPolicy d_policyCallback; + void *d_policyUserdata; + + vrpn_Shared_float64 &set(vrpn_float64, timeval, vrpn_bool isLocalSet); + + virtual vrpn_bool shouldAcceptUpdate(vrpn_float64 newValue, timeval when, + vrpn_bool isLocalSet); + + virtual void sendUpdate(void); + void sendUpdate(vrpn_float64 newValue, timeval when); + void encode(char **buffer, vrpn_int32 *len, vrpn_float64 newValue, + timeval when) const; + void decode(const char **buffer, vrpn_int32 *len, vrpn_float64 *newValue, + timeval *when) const; + + int yankCallbacks(vrpn_bool isLocal); + // must set d_lastUpdate BEFORE calling yankCallbacks() + + int handleUpdate(vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_lamportUpdate(void *, vrpn_HANDLERPARAM); +}; + +class VRPN_API vrpn_Shared_float64_Server : public vrpn_Shared_float64 { + +public: + vrpn_Shared_float64_Server(const char *name, vrpn_float64 defaultValue = 0, + vrpn_int32 defaultMode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_float64_Server(void); + + vrpn_Shared_float64_Server &operator=(vrpn_float64 newValue); + + virtual void bindConnection(vrpn_Connection *); + +protected: +}; + +class VRPN_API vrpn_Shared_float64_Remote : public vrpn_Shared_float64 { + +public: + vrpn_Shared_float64_Remote(const char *name, vrpn_float64 defaultValue = 0, + vrpn_int32 defaultMode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_float64_Remote(void); + + vrpn_Shared_float64_Remote &operator=(vrpn_float64 newValue); + + virtual void bindConnection(vrpn_Connection *); +}; + +class VRPN_API vrpn_Shared_String : public vrpn_SharedObject { + +public: + vrpn_Shared_String(const char *name, const char *defaultValue = NULL, + vrpn_int32 mode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_String(void); + + // ACCESSORS + + const char *value(void) const; + operator const char *() const; + + // MANIPULATORS + + vrpn_Shared_String &operator=(const char *newValue); + // calls set(newValue, now); + + virtual vrpn_Shared_String &set(const char *newValue, timeval when); + // calls protected set (newValue, when, vrpn_TRUE); + + void register_handler(vrpnSharedStringCallback, void *); + void unregister_handler(vrpnSharedStringCallback, void *); + void register_handler(vrpnTimedSharedStringCallback, void *); + void unregister_handler(vrpnTimedSharedStringCallback, void *); + // Callbacks are (currently) called *AFTER* the assignment + // has been made, so any check of the value of their shared int + // will return newValue + + void setSerializerPolicy(vrpn_SerializerPolicy policy = vrpn_ACCEPT, + vrpnSharedStringSerializerPolicy f = NULL, + void *userdata = NULL); + +protected: + char *d_value; + + // callback code + // Could generalize this by making a class that gets passed + // a vrpn_HANDLERPARAM and passes whatever is needed to its callback, + // but it's not worth doing that unless we need a third or fourth + // kind of callback. + struct callbackEntry { + vrpnSharedStringCallback handler; + void *userdata; + callbackEntry *next; + }; + callbackEntry *d_callbacks; + struct timedCallbackEntry { + vrpnTimedSharedStringCallback handler; + void *userdata; + timedCallbackEntry *next; + }; + timedCallbackEntry *d_timedCallbacks; + + vrpn_SerializerPolicy d_policy; // default to vrpn_ACCEPT + vrpnSharedStringSerializerPolicy d_policyCallback; + void *d_policyUserdata; + + vrpn_Shared_String &set(const char *, timeval, vrpn_bool isLocalSet); + + virtual vrpn_bool shouldAcceptUpdate(const char *newValue, timeval when, + vrpn_bool isLocalSet); + + virtual void sendUpdate(void); + void sendUpdate(const char *newValue, timeval when); + void encode(char **buffer, vrpn_int32 *len, const char *newValue, + timeval when) const; + void decode(const char **buffer, vrpn_int32 *len, char *newValue, + timeval *when) const; + + int yankCallbacks(vrpn_bool isLocal); + // must set d_lastUpdate BEFORE calling yankCallbacks() + + int handleUpdate(vrpn_HANDLERPARAM); + static int VRPN_CALLBACK handle_lamportUpdate(void *, vrpn_HANDLERPARAM); +}; + +class VRPN_API vrpn_Shared_String_Server : public vrpn_Shared_String { + +public: + vrpn_Shared_String_Server(const char *name, const char *defaultValue = NULL, + vrpn_int32 defaultMode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_String_Server(void); + + vrpn_Shared_String_Server &operator=(const char *); + + virtual void bindConnection(vrpn_Connection *); + +protected: +}; + +class VRPN_API vrpn_Shared_String_Remote : public vrpn_Shared_String { + +public: + vrpn_Shared_String_Remote(const char *name, const char *defaultValue = NULL, + vrpn_int32 defaultMode = VRPN_SO_DEFAULT); + virtual ~vrpn_Shared_String_Remote(void); + + vrpn_Shared_String_Remote &operator=(const char *); + + virtual void bindConnection(vrpn_Connection *); +}; + +#endif // VRPN_SHARED_OBJECT diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Sound.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Sound.h new file mode 100644 index 000000000000..a06ebdc989d4 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Sound.h @@ -0,0 +1,443 @@ +// vrpn_Sound.h +// +// April 12 2000 - ZK + +#ifndef VRPN_SOUND_H + +#include "vrpn_BaseClass.h" // for vrpn_BaseClass +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Text.h" // for vrpn_TEXTCB, etc +#include "vrpn_Types.h" // for vrpn_int32, vrpn_float64, etc + +class VRPN_API vrpn_Connection; +struct vrpn_HANDLERPARAM; + +#define MAX_MATERIAL_NAME_LENGTH 128 +#define MAX_NUMBER_SOUNDS 1024 +#define MAX_NUMBER_MATERIALS 64 +#define MAX_NUMBER_POLYGONS 2048 +#define MAX_FILENAME_LENGTH 2048 + +// everything is on order found in these structs! + +typedef vrpn_int32 vrpn_SoundID; + +typedef struct _vrpn_PoseDef { + vrpn_float64 position[3]; + vrpn_float64 orientation[4]; + _vrpn_PoseDef() + { + position[0] = position[1] = position[2] = 0.0; + orientation[0] = orientation[1] = orientation[2] = 0.0; + orientation[3] = 1.0; + }; +} vrpn_PoseDef; + +typedef struct _vrpn_SoundDef { + vrpn_PoseDef pose; + vrpn_float64 velocity[4]; + vrpn_float64 max_front_dist; + vrpn_float64 min_front_dist; + vrpn_float64 max_back_dist; + vrpn_float64 min_back_dist; + vrpn_float64 cone_inner_angle; + vrpn_float64 cone_outer_angle; + vrpn_float64 cone_gain; + vrpn_float64 dopler_scale; + vrpn_float64 equalization_val; + vrpn_float64 pitch; + vrpn_float32 volume; // Jason Clark calls this volume, but really it is gain! + _vrpn_SoundDef() + : max_front_dist(0) + , min_front_dist(0) + , max_back_dist(0) + , min_back_dist(0) + , cone_inner_angle(0) + , cone_outer_angle(0) + , cone_gain(0) + , dopler_scale(0) + , equalization_val(0) + , pitch(0) + , volume(0) + { velocity[0] = velocity[1] = velocity[2] = velocity[3] = 0.0; }; + +} vrpn_SoundDef; + +typedef struct _vrpn_ListenerDef { + vrpn_PoseDef pose; + vrpn_float64 velocity[4]; +} vrpn_ListenerDef; + +typedef struct _vrpn_MaterialDef { + char material_name[MAX_MATERIAL_NAME_LENGTH]; + vrpn_float64 transmittance_gain; + vrpn_float64 transmittance_highfreq; + vrpn_float64 reflectance_gain; + vrpn_float64 reflectance_highfreq; +} vrpn_MaterialDef; + +typedef struct _vrpn_QuadDef { + vrpn_int32 subQuad; // really a bool + vrpn_float64 openingFactor; + vrpn_int32 tag; + vrpn_float64 vertices[4][3]; + char material_name[MAX_MATERIAL_NAME_LENGTH]; +} vrpn_QuadDef; + +typedef struct _vrpn_TriDef { + vrpn_int32 subTri; + vrpn_float64 openingFactor; + vrpn_int32 tag; + vrpn_float64 vertices[3][3]; + char material_name[MAX_MATERIAL_NAME_LENGTH]; +} vrpn_TriDef; + +class VRPN_API vrpn_Sound : public vrpn_BaseClass { + +public: + vrpn_Sound(const char *name, vrpn_Connection *c); + ~vrpn_Sound(); + +protected: + vrpn_int32 + load_sound_local; // ID of message to load a sound from server side + vrpn_int32 + load_sound_remote; // ID of message to load a sound from client side + vrpn_int32 unload_sound; // ID of message to unload a sound + vrpn_int32 play_sound; // ID of message to play a sound + vrpn_int32 stop_sound; // ID of message to stop a sound + vrpn_int32 + change_sound_status; // ID of message to change the sound's status + vrpn_int32 + set_listener_pose; // ID of message to set the listener's pos/orient + vrpn_int32 + set_listener_velocity; // ID of message to set the listener's velocity + vrpn_int32 set_sound_pose; // + vrpn_int32 set_sound_velocity; // + vrpn_int32 set_sound_distanceinfo; // + vrpn_int32 set_sound_coneinfo; // + vrpn_int32 set_sound_doplerfactor; // + vrpn_int32 set_sound_eqvalue; // + vrpn_int32 set_sound_pitch; + vrpn_int32 set_sound_volume; // + + vrpn_int32 load_model_local; // load model file from server side + vrpn_int32 load_model_remote; // load model file from client side + vrpn_int32 load_polyquad; // ID of message to load a quad polygon + vrpn_int32 load_polytri; // ID of message to load a tri polygon + vrpn_int32 load_material; // ID of message to load a material definition + vrpn_int32 set_polyquad_vertices; + vrpn_int32 set_polytri_vertices; + vrpn_int32 set_poly_openingfactor; + vrpn_int32 set_poly_material; + + vrpn_int32 receive_text_message; + + struct timeval timestamp; // Current timestamp + + int register_types(void); + + /*All encodes and decodes functions are for the purpose of setting up + messages to be sent over the network properly (ie to put them in one + char buffer and to put them in proper network order and for getting + the messages back into a usable format once they have been received*/ + + /*Note encodeSound allocates space dynamically for buf, it is your + responsibility to free it up*/ + vrpn_int32 encodeSound_local(const char *filename, const vrpn_SoundID id, + const vrpn_SoundDef soundDef, char **buf); + /*Note decodeSound allocates space dynamically for filename, it is your + responsibility to free it up*/ + vrpn_int32 decodeSound_local(const char *buf, char **filename, + vrpn_SoundID *id, vrpn_SoundDef *soundDef, + const int payload); + + // These two are not supported yet! + vrpn_int32 encodeSound_remote(const char *filename, const vrpn_SoundID id, + char **buf); + vrpn_int32 decodeSound_remote(const char *buf, char **filename, + vrpn_SoundID *id, const int payload); + + vrpn_int32 encodeSoundID(const vrpn_SoundID id, char *buf); + vrpn_int32 decodeSoundID(const char *buf, vrpn_SoundID *id); + vrpn_int32 encodeSoundDef(const vrpn_SoundDef sound, const vrpn_SoundID id, + const vrpn_int32 repeat, char *buf); + vrpn_int32 decodeSoundDef(const char *buf, vrpn_SoundDef *sound, + vrpn_SoundID *id, vrpn_int32 *repeat); + vrpn_int32 encodeSoundPlay(const vrpn_SoundID id, const vrpn_int32 repeat, + char *buf); + vrpn_int32 decodeSoundPlay(const char *buf, vrpn_SoundID *id, + vrpn_int32 *repeat); + vrpn_int32 encodeListenerVelocity(const vrpn_float64 *velocity, char *buf); + vrpn_int32 decodeListenerVelocity(const char *buf, vrpn_float64 *velocity); + vrpn_int32 encodeListenerPose(const vrpn_PoseDef pose, char *buf); + vrpn_int32 decodeListenerPose(const char *buf, vrpn_PoseDef *pose); + + vrpn_int32 encodeSoundPose(const vrpn_PoseDef pose, const vrpn_SoundID id, + char *buf); + vrpn_int32 decodeSoundPose(const char *buf, vrpn_PoseDef *pose, + vrpn_SoundID *id); + vrpn_int32 encodeSoundVelocity(const vrpn_float64 *velocity, + const vrpn_SoundID id, char *buf); + vrpn_int32 decodeSoundVelocity(const char *buf, vrpn_float64 *velocity, + vrpn_SoundID *id); + vrpn_int32 encodeSoundDistInfo(const vrpn_float64 min_back, + const vrpn_float64 max_back, + const vrpn_float64 min_front, + const vrpn_float64 max_front, + const vrpn_SoundID id, char *buf); + vrpn_int32 decodeSoundDistInfo(const char *buf, vrpn_float64 *min_back, + vrpn_float64 *max_back, + vrpn_float64 *min_front, + vrpn_float64 *max_front, vrpn_SoundID *id); + vrpn_int32 encodeSoundConeInfo(const vrpn_float64 cone_inner_angle, + const vrpn_float64 cone_outer_angle, + const vrpn_float64 cone_gain, + const vrpn_SoundID id, char *buf); + vrpn_int32 decodeSoundConeInfo(const char *buf, + vrpn_float64 *cone_inner_angle, + vrpn_float64 *cone_outer_angle, + vrpn_float64 *cone_gain, vrpn_SoundID *id); + vrpn_int32 encodeSoundDoplerScale(const vrpn_float64 doplerfactor, + const vrpn_SoundID id, char *buf); + vrpn_int32 decodeSoundDoplerScale(const char *buf, + vrpn_float64 *doplerfactor, + vrpn_SoundID *id); + vrpn_int32 encodeSoundEqFactor(const vrpn_float64 eqfactor, + const vrpn_SoundID id, char *buf); + vrpn_int32 decodeSoundEqFactor(const char *buf, vrpn_float64 *eqfactor, + vrpn_SoundID *id); + vrpn_int32 encodeSoundPitch(const vrpn_float64 pitch, const vrpn_SoundID id, + char *buf); + vrpn_int32 decodeSoundPitch(const char *buf, vrpn_float64 *pitch, + vrpn_SoundID *id); + vrpn_int32 encodeSoundVolume(const vrpn_float64 volume, + const vrpn_SoundID id, char *buf); + vrpn_int32 decodeSoundVolume(const char *buf, vrpn_float64 *volume, + vrpn_SoundID *id); + + vrpn_int32 encodeLoadModel_local(const char *filename, char **buf); + vrpn_int32 decodeLoadModel_local(const char *buf, char **filename, + const int payload); + + // Remote stuff not supported yet! + vrpn_int32 encodeLoadModel_remote(const char *filename, char **buf); + vrpn_int32 decodeLoadModel_remote(const char *buf, char **filename, + const int payload); + + vrpn_int32 encodeLoadPolyQuad(const vrpn_QuadDef quad, char *buf); + vrpn_int32 decodeLoadPolyQuad(const char *buf, vrpn_QuadDef *quad); + vrpn_int32 encodeLoadPolyTri(const vrpn_TriDef tri, char *buf); + vrpn_int32 decodeLoadPolyTri(const char *buf, vrpn_TriDef *tri); + vrpn_int32 encodeLoadMaterial(const vrpn_int32 id, + const vrpn_MaterialDef material, char *buf); + vrpn_int32 decodeLoadMaterial(const char *buf, vrpn_MaterialDef *material, + vrpn_int32 *id); + vrpn_int32 encodeSetQuadVert(const vrpn_float64 vertices[4][3], + const vrpn_int32 tag, char *buf); + vrpn_int32 decodeSetQuadVert(const char *buf, + vrpn_float64 (*vertices)[4][3], + vrpn_int32 *tag); + vrpn_int32 encodeSetTriVert(const vrpn_float64 vertices[3][3], + const vrpn_int32 tag, char *buf); + vrpn_int32 decodeSetTriVert(const char *buf, vrpn_float64 (*vertices)[3][3], + vrpn_int32 *tag); + vrpn_int32 encodeSetPolyOF(const vrpn_float64 openingfactor, + const vrpn_int32 tag, char *buf); + vrpn_int32 decodeSetPolyOF(const char *buf, vrpn_float64 *openingfactor, + vrpn_int32 *tag); + vrpn_int32 encodeSetPolyMaterial(const char *material, const vrpn_int32 tag, + char *buf); + vrpn_int32 decodeSetPolyMaterial(const char *buf, char **material, + vrpn_int32 *tag, const int payload); +}; + +class VRPN_API vrpn_Sound_Client : public vrpn_Sound, + public vrpn_Text_Receiver { +public: + vrpn_Sound_Client(const char *name, vrpn_Connection *c); + ~vrpn_Sound_Client(); + + // This command starts a sound playing, the repeat value indicates how + // many times to play it. Continuously if repeat is set to 0 + vrpn_int32 playSound(const vrpn_SoundID id, vrpn_int32 repeat); + vrpn_int32 stopSound(const vrpn_SoundID id); + // Loads a sound into memory on the server side, returns the ID value to be + // used to refer to the sound from now on. Pass in the path and filename + vrpn_SoundID loadSound(const char *sound, const vrpn_SoundID id, + const vrpn_SoundDef soundDef); + vrpn_int32 unloadSound(const vrpn_SoundID id); + + // All the functions with change and sound in them, can change either an + // already playing sound or one yet to be played + vrpn_int32 setSoundVolume(const vrpn_SoundID id, const vrpn_float64 volume); + vrpn_int32 setSoundPose(const vrpn_SoundID id, vrpn_float64 position[3], + vrpn_float64 orientation[4]); + vrpn_int32 setSoundVelocity(const vrpn_SoundID id, + const vrpn_float64 velocity[4]); + vrpn_int32 setSoundDistances(const vrpn_SoundID id, + const vrpn_float64 max_front_dist, + const vrpn_float64 min_front_dist, + const vrpn_float64 max_back_dist, + const vrpn_float64 min_back_dist); + vrpn_int32 setSoundConeInfo(const vrpn_SoundID id, + const vrpn_float64 inner_angle, + const vrpn_float64 outer_angle, + const vrpn_float64 gain); + + vrpn_int32 setSoundDopScale(const vrpn_SoundID id, vrpn_float64 dopfactor); + vrpn_int32 setSoundEqValue(const vrpn_SoundID id, vrpn_float64 eq_value); + vrpn_int32 setSoundPitch(const vrpn_SoundID id, vrpn_float64 pitch); + + vrpn_int32 setListenerPose(const vrpn_float64 position[3], + const vrpn_float64 orientation[4]); + vrpn_int32 setListenerVelocity(const vrpn_float64 velocity[4]); + + vrpn_int32 LoadModel_local(const char *filename); + + // Remote stuff not supported yet! + vrpn_int32 LoadModel_remote(const char *data); + + vrpn_int32 LoadPolyQuad(const vrpn_QuadDef quad); + vrpn_int32 LoadPolyTri(const vrpn_TriDef tri); + vrpn_int32 LoadMaterial(const vrpn_int32 id, + const vrpn_MaterialDef material); + + vrpn_int32 setMaterialName(const int id, const char *materialname); + vrpn_int32 setMaterialTransGain(const int id, + const vrpn_float64 transmittance_gain); + vrpn_int32 setMaterialTransHF(const int id, + const vrpn_float64 transmittance_hf); + vrpn_int32 setMaterialReflGain(const int id, + const vrpn_float64 reflectance_gain); + vrpn_int32 setMaterialReflHF(const int id, + const vrpn_float64 reflectance_hf); + + vrpn_int32 setPolyOF(const int id, const vrpn_float64 OF); + vrpn_int32 setQuadVertices(const int id, const vrpn_float64 vertices[4][3]); + vrpn_int32 setPolyMaterialName(const int id, const char *materialname); + + vrpn_int32 setTriVertices(const int id, const vrpn_float64 vertices[3][3]); + + virtual void mainloop(); + + virtual void receiveTextMessage(const char *message, vrpn_uint32 type, + vrpn_uint32 level, struct timeval msg_time); + +protected: +private: + static void VRPN_CALLBACK + handle_receiveTextMessage(void *userdata, const vrpn_TEXTCB t); +}; + +/*Note on the server design + The server is designed in such a way that it expects a sub-class that is + implemented + that actually implements sound functionality to have certain functions that it + can + call to tell the child to play, load, whatever. This parent server class, + handles + all of the callback functionality and decoding, allowing child classes to only + have + to worry about sound functionality*/ +#ifndef VRPN_CLIENT_ONLY +class VRPN_API vrpn_Sound_Server : public vrpn_Sound, public vrpn_Text_Sender { +public: + vrpn_Sound_Server(const char *name, vrpn_Connection *c); + ~vrpn_Sound_Server(); + + virtual void playSound(vrpn_SoundID id, vrpn_int32 repeat, + vrpn_SoundDef soundDef) = 0; + virtual void loadSoundLocal(char *filename, vrpn_SoundID id, + vrpn_SoundDef soundDef) = 0; + virtual void loadSoundRemote(char *file, vrpn_SoundID id, + vrpn_SoundDef soundDef) = 0; + virtual void stopSound(vrpn_SoundID id) = 0; + virtual void unloadSound(vrpn_SoundID id) = 0; + virtual void changeSoundStatus(vrpn_SoundID id, vrpn_SoundDef soundDef) = 0; + virtual void setListenerPose(vrpn_PoseDef pose) = 0; + virtual void setListenerVelocity(vrpn_float64 *velocity) = 0; + + virtual void setSoundPose(vrpn_SoundID id, vrpn_PoseDef pose) = 0; + virtual void setSoundVelocity(vrpn_SoundID id, vrpn_float64 *velocity) = 0; + virtual void setSoundDistInfo(vrpn_SoundID id, vrpn_float64 *distinfo) = 0; + virtual void setSoundConeInfo(vrpn_SoundID id, vrpn_float64 *coneinfo) = 0; + + virtual void setSoundDoplerFactor(vrpn_SoundID id, + vrpn_float64 doplerfactor) = 0; + virtual void setSoundEqValue(vrpn_SoundID id, vrpn_float64 eqvalue) = 0; + virtual void setSoundPitch(vrpn_SoundID id, vrpn_float64 pitch) = 0; + virtual void setSoundVolume(vrpn_SoundID id, vrpn_float64 volume) = 0; + virtual void loadModelLocal(const char *filename) = 0; + virtual void loadModelRemote() = 0; // not supported + virtual void loadPolyQuad(vrpn_QuadDef *quad) = 0; + virtual void loadPolyTri(vrpn_TriDef *tri) = 0; + virtual void loadMaterial(vrpn_MaterialDef *material, vrpn_int32 id) = 0; + virtual void setPolyQuadVertices(vrpn_float64 vertices[4][3], + const vrpn_int32 id) = 0; + virtual void setPolyTriVertices(vrpn_float64 vertices[3][3], + const vrpn_int32 id) = 0; + virtual void setPolyOF(vrpn_float64 OF, vrpn_int32 tag) = 0; + virtual void setPolyMaterial(const char *material, vrpn_int32 tag) = 0; + +protected: +private: + static int VRPN_CALLBACK + handle_loadSoundLocal(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_loadSoundRemote(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_unloadSound(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_playSound(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_stopSound(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_changeSoundStatus(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setListenerPose(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setListenerVelocity(void *userdata, vrpn_HANDLERPARAM p); + + static int VRPN_CALLBACK + handle_setSoundPose(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setSoundVelocity(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setSoundDistanceinfo(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setSoundConeinfo(void *userdata, vrpn_HANDLERPARAM p); + + static int VRPN_CALLBACK + handle_setSoundDoplerfactor(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setSoundEqvalue(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setSoundPitch(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setSoundVolume(void *userdata, vrpn_HANDLERPARAM p); + + static int VRPN_CALLBACK + handle_loadModelLocal(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_loadModelRemote(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_loadPolyquad(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_loadPolytri(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_loadMaterial(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setPolyquadVertices(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setPolytriVertices(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setPolyOpeningfactor(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_setPolyMaterial(void *userdata, vrpn_HANDLERPARAM p); +}; +#endif //#ifndef VRPN_CLIENT_ONLY + +#define VRPN_SOUND_H +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Text.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Text.h new file mode 100644 index 000000000000..597d0d91196c --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Text.h @@ -0,0 +1,102 @@ +/* vrpn_Text.h + Definition of user-level access to the text sending and retrieving + functions within VRPN. These are wrappers around the vrpn_BaseClass + routines, since basic text functions have been pulled into these + classes. +*/ + +#ifndef VRPN_TEXT_H +#include // for NULL + +#include "vrpn_BaseClass.h" // for vrpn_BaseClass, etc +#include "vrpn_Configure.h" // for VRPN_API, VRPN_CALLBACK +#include "vrpn_Connection.h" // for vrpn_Connection, etc +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_uint32 + +// text-message time value meaning "go find out what time it is right now" +const struct timeval vrpn_TEXT_NOW = {0, 0}; + +/// Structure passed back to user-level code from a vrpn_Text_Receiver. +typedef struct _vrpn_TEXTCB { + struct timeval msg_time; // Time of the message + char message[vrpn_MAX_TEXT_LEN]; // The message + vrpn_TEXT_SEVERITY type; + vrpn_uint32 level; +} vrpn_TEXTCB; + +/// Description of the callback function type. +typedef void(VRPN_CALLBACK *vrpn_TEXTHANDLER)(void *userdata, + const vrpn_TEXTCB info); + +//---------------------------------------------------------- +//************** Users deal with the following ************* + +/// Allows a user to send text messages from a device (usually, +// the send_text_message() function is protected). It provides +// the needed function definitions for vrpn_BaseClass. + +class VRPN_API vrpn_Text_Sender : public vrpn_BaseClass { +public: + vrpn_Text_Sender(const char *name, vrpn_Connection *c = NULL) + : vrpn_BaseClass(name, c) + { + init(); + }; + + /// Mainloop the connection to send the message. + void mainloop(void) + { + server_mainloop(); + if (d_connection) d_connection->mainloop(); + }; + + /// Send a text message. + int send_message(const char *msg, + vrpn_TEXT_SEVERITY type = vrpn_TEXT_NORMAL, + vrpn_uint32 level = 0, + const struct timeval time = vrpn_TEXT_NOW); + +protected: + /// No types to register beyond the text, which is done in BaseClass. + virtual int register_types(void) { return 0; }; +}; + +/// Allows a user to handle text messages directly, in addition to having the +// standard VRPN printing functions handle them. + +class VRPN_API vrpn_Text_Receiver : public vrpn_BaseClass { +public: + vrpn_Text_Receiver(const char *name, vrpn_Connection *c = NULL); + virtual ~vrpn_Text_Receiver(void); + virtual int register_message_handler(void *userdata, + vrpn_TEXTHANDLER handler) + { + return d_callback_list.register_handler(userdata, handler); + }; + + virtual int unregister_message_handler(void *userdata, + vrpn_TEXTHANDLER handler) + { + return d_callback_list.unregister_handler(userdata, handler); + } + + virtual void mainloop(void) + { + if (d_connection) { + d_connection->mainloop(); + }; + client_mainloop(); + }; + +protected: + static int VRPN_CALLBACK + handle_message(void *userdata, vrpn_HANDLERPARAM p); + vrpn_Callback_List d_callback_list; + + /// No types to register beyond the text, which is done in BaseClass. + virtual int register_types(void) { return 0; }; +}; + +#define VRPN_TEXT_H +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Thread.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Thread.h new file mode 100644 index 000000000000..4141467b1d82 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Thread.h @@ -0,0 +1,245 @@ +/** @file + @brief Header containing vrpn_Thread, vrpn_Semaphore (formerly in + vrpn_Shared.h), as well as a lock-guard class. + + Semaphore and Thread classes derived from Hans Weber's classes from UNC. + Don't let the existence of a Thread class fool you into thinking + that VRPN is thread-safe. This and the Semaphore are included as + building blocks towards making your own code thread-safe. They are + here to enable the vrpn_Imager_Logger class to do its thing. + + @date 2015 + + @author + Sensics, Inc. + +*/ + +// Copyright 2015 Sensics, Inc. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef INCLUDED_vrpn_Thread_h_GUID_A455652F_72CE_4F8A_859E_543489012D01 +#define INCLUDED_vrpn_Thread_h_GUID_A455652F_72CE_4F8A_859E_543489012D01 + +// Internal Includes +#include "vrpn_Configure.h" // for VRPN_API + +// Library/third-party includes +// - none + +// Standard includes + +#if defined(sgi) || (defined(_WIN32) && !defined(__CYGWIN__)) || \ + defined(linux) || defined(__APPLE__) +#define vrpn_THREADS_AVAILABLE +#else +#undef vrpn_THREADS_AVAILABLE +#endif + +// multi process stuff +#if defined(sgi) +#include +#include +#elif defined(_WIN32) +#include "vrpn_WindowsH.h" +#include +#else +#include // for pthread_t +#include // for sem_t +#endif + +// make the SGI compile without tons of warnings +#ifdef sgi +#pragma set woff 1110, 1424, 3201 +#endif + +// and reset the warnings +#ifdef sgi +#pragma reset woff 1110, 1424, 3201 +#endif + +class VRPN_API vrpn_Semaphore { +public: + /// @brief constructor - mutex by default (0 is a sync primitive) + vrpn_Semaphore(int cNumResources = 1); + + /// @brief destructor + ~vrpn_Semaphore(); + + /// @brief routine to reset it (true on success, false on failure) + /// (may create new semaphore) + bool reset(int cNumResources = 1); + + /// @brief Blocking acquire of resource. ("down") + /// @return 1 when it has acquired the resource, -1 on fail + int p(); + + /// @brief Release of resource. ("up") + /// @return 0 when it has released the resource, -1 on fail + int v(); + + /// @brief Non-blocking attempt to acquire resource ("down") + /// @return 0 if it could not access the resource + /// and 1 if it could (-1 on fail) + int condP(); + + /// @brief read values + int numResources(); + +private: + /// @brief non-copyable + vrpn_Semaphore(const vrpn_Semaphore &); + /// @brief non-assignable + vrpn_Semaphore & operator=(const vrpn_Semaphore &); + /// @name common init and destroy routines + /// @{ + bool init(); + bool destroy(); + /// @} + + int cResources; + + // arch specific details +#ifdef sgi + // single mem area for dynamically alloced shared mem + static usptr_t *ppaArena; + static void allocArena(); + + // the semaphore struct in the arena + usema_t *ps; + ulock_t l; + bool fUsingLock; +#elif defined(_WIN32) + HANDLE hSemaphore; +#else + sem_t *semaphore; // Posix +#endif +}; + +namespace vrpn { + struct try_to_lock_t { + }; + + /// @brief Dummy variable to pass to SemaphoreGuard to indicate we only want + /// a conditional lock. + const try_to_lock_t try_to_lock = {}; + /// @brief An RAII lock/guard class for vrpn_Semaphore + class VRPN_API SemaphoreGuard { + public: + /// @brief Constructor that locks (p) the semaphore + explicit SemaphoreGuard(vrpn_Semaphore &sem); + + /// @brief overload that only tries to lock (condP) - doesn't block. + SemaphoreGuard(vrpn_Semaphore &sem, try_to_lock_t); + + /// @brief Destructor that unlocks if we've locked. + ~SemaphoreGuard(); + + /// @brief Checks to see if we locked. + bool locked() const { return locked_; } + + /// @brief Locks the semaphore, if we haven't locked it already. + void lock(); + + /// @brief Tries to lock - returns true if we locked it. + bool try_to_lock(); + + /// @brief Unlocks the resource, if we have locked it. + void unlock(); + + private: + void handleLockResult_(int result); + /// @brief non-copyable + SemaphoreGuard(SemaphoreGuard const &); + /// @brief non-assignable + SemaphoreGuard &operator=(SemaphoreGuard const &); + bool locked_; + vrpn_Semaphore &sem_; + }; + +} // namespace vrpn + +// A ptr to this struct will be passed to the +// thread function. The user data ptr will be in pvUD. +// (There used to be a non-functional semaphore object +// also in this structure, but it was removed. This leaves +// a struct with only one element, which is a pain but +// at least it doesn't break existing code. If we need +// to add something else later, there is a place for it. + +// The user should create and manage any semaphore needed +// to handle access control to the userdata. + +struct VRPN_API vrpn_ThreadData { + void *pvUD; +}; + +typedef void(*vrpn_THREAD_FUNC)(vrpn_ThreadData &threadData); + +// Don't let the existence of a Thread class fool you into thinking +// that VRPN is thread-safe. This and the Semaphore are included as +// building blocks towards making your own code thread-safe. They are +// here to enable the vrpn_Imager_Stream_Buffer class to do its thing. +class VRPN_API vrpn_Thread { +public: + // args are the routine to run in the thread + // a ThreadData struct which will be passed into + // the thread (it will be passed as a void *). + vrpn_Thread(vrpn_THREAD_FUNC pfThread, vrpn_ThreadData td); + ~vrpn_Thread(); + +#if defined(sgi) + typedef unsigned long thread_t; +#elif defined(_WIN32) + typedef uintptr_t thread_t; +#else + typedef pthread_t thread_t; +#endif + + // start/kill the thread (true on success, false on failure) + bool go(); + bool kill(); + + // thread info: check if running, get proc id + bool running(); + thread_t pid(); + + // run-time user function to test if threads are available + // (same value as #ifdef THREADS_AVAILABLE) + static bool available(); + + // Number of processors available on this machine. + static unsigned number_of_processors(); + + // This can be used to change the ThreadData user data ptr + // between calls to go (ie, when a thread object is used + // many times with different args). This will take + // effect the next time go() is called. + void userData(void *pvNewUserData); + void *userData(); + +protected: + // user func and data ptrs + void(*pfThread)(vrpn_ThreadData &ThreadData); + vrpn_ThreadData td; + + // utility func for calling the specified function. + static void threadFuncShell(void *pvThread); + + // Posix version of the utility function, makes the + // function prototype match. + static void *threadFuncShellPosix(void *pvThread); + + // the process id + thread_t threadID; +}; + +// Returns true if they work and false if they do not. +extern bool vrpn_test_threads_and_semaphores(void); + + + +#endif // INCLUDED_vrpn_Thread_h_GUID_A455652F_72CE_4F8A_859E_543489012D01 + diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Tracker.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Tracker.h new file mode 100644 index 000000000000..0b4bdc14b976 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Tracker.h @@ -0,0 +1,518 @@ +#ifndef vrpn_TRACKER_H +#define vrpn_TRACKER_H +#include // for NULL, FILE + +// NOTE: a vrpn tracker must call user callbacks with tracker data (pos and +// ori info) which represent the transformation xfSourceFromSensor. +// This means that the pos info is the position of the origin of +// the sensor coord sys in the source coord sys space, and the +// quat represents the orientation of the sensor relative to the +// source space (ie, its value rotates the source's axes so that +// they coincide with the sensor's) +// Positions from all trackers in VRPN are reported in meters. +// Velocities are reported in meters/second. +// Accelerations are reported in meters/second/second. +// These are all reported in three-element double arrays +// in the order (X=0, Y=1, Z=2). +// They are translated into this format from the native format for each device. +// Orientations from all trackers in VRPN are reported in quaternions +// (see Quatlib for more info) in four-element double arrays +// in the order (X=0, Y=1, Z=2, W=3). +// They are translated into this format from the native format for each device. + +// to use time synched tracking, just pass in a sync connection to the +// client and the server + +#include "vrpn_BaseClass.h" // for vrpn_Callback_List, etc +#include "vrpn_Configure.h" // for VRPN_CALLBACK, VRPN_API, etc +#include "vrpn_Connection.h" +#include "vrpn_Shared.h" // for timeval +#include "vrpn_Types.h" // for vrpn_float64, vrpn_int32, etc + +class VRPN_API vrpn_RedundantTransmission; + +// tracker status flags +const int vrpn_TRACKER_SYNCING = (3); +const int vrpn_TRACKER_AWAITING_STATION = (2); +const int vrpn_TRACKER_REPORT_READY = (1); +const int vrpn_TRACKER_PARTIAL = (0); +const int vrpn_TRACKER_RESETTING = (-1); +const int vrpn_TRACKER_FAIL = (-2); + +// index for the change_list that should be called for all sensors. +// Not an in-range index. +const int vrpn_ALL_SENSORS = -1; + +typedef vrpn_float64 vrpn_Tracker_Pos[3]; +typedef vrpn_float64 vrpn_Tracker_Quat[4]; + +class VRPN_API vrpn_Tracker : public vrpn_BaseClass { +public: + // vrpn_Tracker.cfg, in the "local" directory, is the default config file + // . You can specify a different config file in the constructor. When + // you do this, you must also specify a vrpn_Connection. Pass in NULL + // if you don't have one. This awkwardness is because C++ requires that + // only the rightmost arguments can use the default values, and that the + // order of arguments must match the base class :( + vrpn_Tracker(const char *name, vrpn_Connection *c = NULL, + const char *tracker_cfg_file_name = NULL); + + virtual ~vrpn_Tracker(void); + + int read_config_file(FILE *config_file, const char *tracker_name); + void print_latest_report(void); + // a tracker server should call the following to register the + // default xform and workspace request handlers + int register_server_handlers(void); + void get_local_t2r(vrpn_float64 *vec, vrpn_float64 *quat); + void get_local_u2s(vrpn_int32 sensor, vrpn_float64 *vec, + vrpn_float64 *quat); + static int VRPN_CALLBACK + handle_t2r_request(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_u2s_request(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_workspace_request(void *userdata, vrpn_HANDLERPARAM p); + // static int VRPN_CALLBACK handle_update_rate_request (void *, + // vrpn_HANDLERPARAM); + +protected: + vrpn_int32 position_m_id; // ID of tracker position message + vrpn_int32 velocity_m_id; // ID of tracker velocity message + vrpn_int32 accel_m_id; // ID of tracker acceleration message + vrpn_int32 tracker2room_m_id; // ID of tracker tracker2room message + vrpn_int32 unit2sensor_m_id; // ID of tracker unit2sensor message + vrpn_int32 request_t2r_m_id; // ID of tracker2room request message + vrpn_int32 request_u2s_m_id; // ID of unit2sensor request message + vrpn_int32 request_workspace_m_id; // ID of workspace request message + vrpn_int32 workspace_m_id; // ID of workspace message + vrpn_int32 update_rate_id; // ID of update rate message + vrpn_int32 connection_dropped_m_id; // ID of connection dropped message + vrpn_int32 reset_origin_m_id; // ID of reset origin message + + // Description of the next report to go out + vrpn_int32 d_sensor; // Current sensor + vrpn_float64 pos[3], d_quat[4]; // Current pose, (x,y,z), (qx,qy,qz,qw) + vrpn_float64 vel[3], vel_quat[4]; // Cur velocity and dQuat/vel_quat_dt + vrpn_float64 vel_quat_dt; // delta time (in secs) for vel_quat + vrpn_float64 acc[3], acc_quat[4]; // Cur accel and d2Quat/acc_quat_dt2 + vrpn_float64 acc_quat_dt; // delta time (in secs) for acc_quat + struct timeval timestamp; // Current timestamp + vrpn_int32 frame_count; // Current framecount + + // The timestamp that the last report was received (Used by the Liberty + // Driver) + // Other trackers use timestamp as the watchdog, however due to variable USB + // latency the Liberty driver uses the device timestamp and not the computer + // clock + // at the time the report was received. This however can drift + // from the computer time, and hence it can cause a reset when things are + // working fine + struct timeval watchdog_timestamp; + + vrpn_float64 tracker2room[3], tracker2room_quat[4]; // Current t2r xform + vrpn_int32 num_sensors; + + // Arrays of values, one per sensor. Includes function to ensure there are + // enough there for a specified number of sensors. + vrpn_Tracker_Pos *unit2sensor; + vrpn_Tracker_Quat *unit2sensor_quat; // Current u2s xforms + unsigned num_unit2sensors; + bool ensure_enough_unit2sensors(unsigned num); + + // bounding box for the tracker workspace (in tracker space) + // these are the points with (x,y,z) minimum and maximum + // note: we assume the bounding box edges are aligned with the tracker + // coordinate system + vrpn_float64 workspace_min[3], workspace_max[3]; + + int status; // What are we doing? + + virtual int register_types(void); //< Called by BaseClass init() + virtual int encode_to(char *buf); // Encodes the position report + // Not all trackers will call the velocity and acceleration packers + virtual int encode_vel_to(char *buf); // Encodes the velocity report + virtual int encode_acc_to(char *buf); // Encodes the acceleration report + virtual int encode_tracker2room_to(char *buf); // Encodes the tracker2room + virtual int encode_unit2sensor_to(char *buf); // and unit2sensor xforms + virtual int encode_workspace_to(char *buf); // Encodes workspace info +}; + +#ifndef VRPN_CLIENT_ONLY +#define VRPN_TRACKER_BUF_SIZE 100 + +class VRPN_API vrpn_Tracker_Serial : public vrpn_Tracker { +public: + vrpn_Tracker_Serial(const char *name, vrpn_Connection *c, + const char *port = "/dev/ttyS1", long baud = 38400); + virtual ~vrpn_Tracker_Serial(); + +protected: + char portname[VRPN_TRACKER_BUF_SIZE]; + long baudrate; + int serial_fd; + + unsigned char buffer[VRPN_TRACKER_BUF_SIZE]; // Characters read in from the + // tracker so far + vrpn_uint32 bufcount; // How many characters in the buffer? + + /// Gets a report if one is available, returns 0 if not, 1 if complete + /// report. + virtual int get_report(void) = 0; + + // Sends the report that was just read. + virtual void send_report(void); + + /// Reset the tracker. + virtual void reset(void) = 0; + +public: + /// Uses the get_report, send_report, and reset routines to implement a + /// server + virtual void mainloop(); +}; + +// This driver uses the VRPN-preferred LibUSB-1.0 to control the device. +#if defined(VRPN_USE_LIBUSB_1_0) +struct libusb_device_handle; // IWYU pragma: keep +struct libusb_context; // IWYU pragma: keep +#define VRPN_TRACKER_USB_BUF_SIZE 1000 + +class VRPN_API vrpn_Tracker_USB : public vrpn_Tracker { +public: + vrpn_Tracker_USB(const char *name, vrpn_Connection *c, vrpn_uint16 vendor, + vrpn_uint16 product, long baud = 115200); + virtual ~vrpn_Tracker_USB(); + +protected: + struct libusb_device_handle *_device_handle; // Handle for the USB device + struct libusb_context *_context; // LibUSB context used for this device + vrpn_uint16 _vendor; // Vendor ID for usb device + vrpn_uint16 _product; // Product ID for usb device + long _baudrate; + + vrpn_uint8 buffer[VRPN_TRACKER_USB_BUF_SIZE]; // Characters read in from the + // tracker + vrpn_uint32 bufcount; // How many characters in the buffer? + + /// Gets reports if some are available, returns 0 if not, 1 if complete + /// report(s). + virtual int get_report(void) = 0; + + // Sends the report that was just read. + virtual void send_report(void); + + /// Reset the tracker. + virtual void reset(void) = 0; + +public: + /// Uses the get_report, send_report, and reset routines to implement a + /// server + virtual void mainloop(); +}; + +// End of VRPN_USE_LIBUSB_1_0 +#endif + +#endif // VRPN_CLIENT_ONLY + +// This is an example of a tracker server. It basically reports the +// position at the origin with zero velocity and acceleration over and +// over again at the rate requested. It is here mostly as an example of +// how to build a tracker server, and also serves as a test object for +// client codes and VRPN builds. + +class VRPN_API vrpn_Tracker_NULL : public vrpn_Tracker { +public: + vrpn_Tracker_NULL(const char *name, vrpn_Connection *c, + vrpn_int32 sensors = 1, vrpn_float64 Hz = 1.0); + virtual void mainloop(); + + void setRedundantTransmission(vrpn_RedundantTransmission *); + +protected: + vrpn_float64 update_rate; + + vrpn_RedundantTransmission *d_redundancy; +}; + +// This is an example of a tracker server. It stays at the +// origina and spins around the specified axis at the +// specified rate of rotation, reporting orientation and +// orientation velocity at the specified +// rate. It was designed to help test the smoothness of +// rendering for VR systems by providing a ground-truth +// smoothly-rotating tracker source. + +class VRPN_API vrpn_Tracker_Spin : public vrpn_Tracker { +public: + vrpn_Tracker_Spin(const char *name, vrpn_Connection *c, + vrpn_int32 sensors = 1, vrpn_float64 reportRateHz = 1.0, + vrpn_float64 axisX = 0, vrpn_float64 axisY = 0, + vrpn_float64 axisZ = 1, vrpn_float64 spinRateHz = 0.5); + virtual void mainloop(); + +protected: + vrpn_float64 update_rate; + vrpn_float64 x, y, z, spin_rate_Hz; + struct timeval start; +}; + +// This is a tracker server that can be used by an application that +// just wants to generate tracker reports but does not really have +// a tracker device to drive. Similar to the vrpn_Analog_Server, it +// provides a quick and easy way for an application to report things. +// +// The application creates an object of this class, specifying the +// number of sensors and the connection that is to be used. It then +// reports poses (position + quat), pose velocities, and pose +// accelerations as desired using the provided functions. The +// mainloop() function needs to be called periodically even when +// there is nothing to report. + +class VRPN_API vrpn_Tracker_Server : public vrpn_Tracker { +public: + vrpn_Tracker_Server(const char *name, vrpn_Connection *c, + vrpn_int32 sensors = 1); + + /// This function should be called each time through app mainloop. + virtual void mainloop(); + + /// These functions should be called to report changes in state, once per + /// sensor. + virtual int report_pose( + const int sensor, const struct timeval t, + const vrpn_float64 position[3], const vrpn_float64 quaternion[4], + const vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY); + virtual int report_pose_velocity( + const int sensor, const struct timeval t, + const vrpn_float64 position[3], const vrpn_float64 quaternion[4], + const vrpn_float64 interval, + const vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY); + virtual int report_pose_acceleration( + const int sensor, const struct timeval t, + const vrpn_float64 position[3], const vrpn_float64 quaternion[4], + const vrpn_float64 interval, + const vrpn_uint32 class_of_service = vrpn_CONNECTION_LOW_LATENCY); +}; + +//---------------------------------------------------------- +// ************** Users deal with the following ************* + +// User routine to handle a tracker position update. This is called when +// the tracker callback is called (when a message from its counterpart +// across the connection arrives). + +typedef struct _vrpn_TRACKERCB { + struct timeval msg_time; // Time of the report + vrpn_int32 sensor; // Which sensor is reporting + vrpn_float64 pos[3]; // Position of the sensor + vrpn_float64 quat[4]; // Orientation of the sensor +} vrpn_TRACKERCB; +typedef void(VRPN_CALLBACK *vrpn_TRACKERCHANGEHANDLER)( + void *userdata, const vrpn_TRACKERCB info); + +// User routine to handle a tracker velocity update. This is called when +// the tracker callback is called (when a message from its counterpart +// across the connetion arrives). + +typedef struct _vrpn_TRACKERVELCB { + struct timeval msg_time; // Time of the report + vrpn_int32 sensor; // Which sensor is reporting + vrpn_float64 vel[3]; // Velocity of the sensor + vrpn_float64 vel_quat[4]; // Rotation of the sensor per vel_quat_dt + vrpn_float64 vel_quat_dt; // delta time (in secs) for vel_quat +} vrpn_TRACKERVELCB; +typedef void(VRPN_CALLBACK *vrpn_TRACKERVELCHANGEHANDLER)( + void *userdata, const vrpn_TRACKERVELCB info); + +// User routine to handle a tracker acceleration update. This is called when +// the tracker callback is called (when a message from its counterpart +// across the connetion arrives). + +typedef struct _vrpn_TRACKERACCCB { + struct timeval msg_time; // Time of the report + vrpn_int32 sensor; // Which sensor is reporting + vrpn_float64 acc[3]; // Acceleration of the sensor + vrpn_float64 acc_quat[4]; // Change in vel_quat of the sensor per acc_quat_dt + vrpn_float64 acc_quat_dt; // delta time (in secs) for acc_quat + +} vrpn_TRACKERACCCB; +typedef void(VRPN_CALLBACK *vrpn_TRACKERACCCHANGEHANDLER)( + void *userdata, const vrpn_TRACKERACCCB info); + +// User routine to handle a tracker room2tracker xform update. This is called +// when the tracker callback is called (when a message from its counterpart +// across the connection arrives). + +typedef struct _vrpn_TRACKERTRACKER2ROOMCB { + struct timeval msg_time; // Time of the report + vrpn_float64 tracker2room[3]; // position offset + vrpn_float64 tracker2room_quat[4]; // orientation offset +} vrpn_TRACKERTRACKER2ROOMCB; +typedef void(VRPN_CALLBACK *vrpn_TRACKERTRACKER2ROOMCHANGEHANDLER)( + void *userdata, const vrpn_TRACKERTRACKER2ROOMCB info); + +typedef struct _vrpn_TRACKERUNIT2SENSORCB { + struct timeval msg_time; // Time of the report + vrpn_int32 sensor; // Which sensor this is for + vrpn_float64 unit2sensor[3]; // position offset + vrpn_float64 unit2sensor_quat[4]; // orientation offset +} vrpn_TRACKERUNIT2SENSORCB; +typedef void(VRPN_CALLBACK *vrpn_TRACKERUNIT2SENSORCHANGEHANDLER)( + void *userdata, const vrpn_TRACKERUNIT2SENSORCB info); + +typedef struct _vrpn_TRACKERWORKSPACECB { + struct timeval msg_time; // Time of the report + vrpn_float64 workspace_min[3]; // minimum corner of box (tracker CS) + vrpn_float64 workspace_max[3]; // maximum corner of box (tracker CS) +} vrpn_TRACKERWORKSPACECB; +typedef void(VRPN_CALLBACK *vrpn_TRACKERWORKSPACECHANGEHANDLER)( + void *userdata, const vrpn_TRACKERWORKSPACECB info); + +// Structure to hold all of the callback lists for one sensor +// (also used for the "all sensors" sensor). +class vrpn_Tracker_Sensor_Callbacks { +public: + vrpn_Callback_List d_change; + vrpn_Callback_List d_velchange; + vrpn_Callback_List d_accchange; + vrpn_Callback_List d_unit2sensorchange; + + // This class requires deep copies. + void operator=(const vrpn_Tracker_Sensor_Callbacks &from) + { + d_change = from.d_change; + d_velchange = from.d_velchange; + d_accchange = from.d_accchange; + d_unit2sensorchange = from.d_unit2sensorchange; + }; +}; + +// Open a tracker that is on the other end of a connection +// and handle updates from it. This is the type of tracker that user code will +// deal with. + +class VRPN_API vrpn_Tracker_Remote : public vrpn_Tracker { +public: + // The name of the tracker to connect to, including connection name, + // for example "Ceiling_tracker@ceiling.cs.unc.edu". If you already + // have the connection open, you can specify it as the second parameter. + // This allows both servers and clients in the same thread, for example. + // If it is not specified, then the connection will be looked up based + // on the name passed in. + vrpn_Tracker_Remote(const char *name, vrpn_Connection *c = NULL); + + // unregister all of the handlers registered with the connection + virtual ~vrpn_Tracker_Remote(void); + + // request room from tracker xforms + int request_t2r_xform(void); + // request all available sensor from unit xforms + int request_u2s_xform(void); + // request workspace bounding box + int request_workspace(void); + + // set rate of p/v/a updates from the tracker + int set_update_rate(vrpn_float64 samplesPerSecond); + + // reset origin to current tracker location (e.g. - to reinitialize + // a PHANToM in its reset position) + int reset_origin(void); + + // This routine calls the mainloop of the connection it's on + virtual void mainloop(); + + // **** to register handlers for sensor-specific messages: **** + // Default is to register them for all sensors. + + // (un)Register a callback handler to handle a position change + virtual int register_change_handler(void *userdata, + vrpn_TRACKERCHANGEHANDLER handler, + vrpn_int32 sensor = vrpn_ALL_SENSORS); + virtual int unregister_change_handler(void *userdata, + vrpn_TRACKERCHANGEHANDLER handler, + vrpn_int32 sensor = vrpn_ALL_SENSORS); + + // (un)Register a callback handler to handle a velocity change + virtual int register_change_handler(void *userdata, + vrpn_TRACKERVELCHANGEHANDLER handler, + vrpn_int32 sensor = vrpn_ALL_SENSORS); + virtual int unregister_change_handler(void *userdata, + vrpn_TRACKERVELCHANGEHANDLER handler, + vrpn_int32 sensor = vrpn_ALL_SENSORS); + + // (un)Register a callback handler to handle an acceleration change + virtual int register_change_handler(void *userdata, + vrpn_TRACKERACCCHANGEHANDLER handler, + vrpn_int32 sensor = vrpn_ALL_SENSORS); + virtual int unregister_change_handler(void *userdata, + vrpn_TRACKERACCCHANGEHANDLER handler, + vrpn_int32 sensor = vrpn_ALL_SENSORS); + + // (un)Register a callback handler to handle a unit2sensor change + virtual int + register_change_handler(void *userdata, + vrpn_TRACKERUNIT2SENSORCHANGEHANDLER handler, + vrpn_int32 sensor = vrpn_ALL_SENSORS); + virtual int + unregister_change_handler(void *userdata, + vrpn_TRACKERUNIT2SENSORCHANGEHANDLER handler, + vrpn_int32 sensor = vrpn_ALL_SENSORS); + + // **** to get workspace information **** + // (un)Register a callback handler to handle a workspace change + virtual int + register_change_handler(void *userdata, + vrpn_TRACKERWORKSPACECHANGEHANDLER handler) + { + return d_workspacechange_list.register_handler(userdata, handler); + }; + virtual int + unregister_change_handler(void *userdata, + vrpn_TRACKERWORKSPACECHANGEHANDLER handler) + { + return d_workspacechange_list.unregister_handler(userdata, handler); + } + + // (un)Register a callback handler to handle a tracker2room change + virtual int + register_change_handler(void *userdata, + vrpn_TRACKERTRACKER2ROOMCHANGEHANDLER handler) + { + return d_tracker2roomchange_list.register_handler(userdata, handler); + }; + virtual int + unregister_change_handler(void *userdata, + vrpn_TRACKERTRACKER2ROOMCHANGEHANDLER handler) + { + return d_tracker2roomchange_list.unregister_handler(userdata, handler); + }; + +protected: + // Callbacks with one per sensor (plus one for "all") + vrpn_Tracker_Sensor_Callbacks all_sensor_callbacks; + vrpn_Tracker_Sensor_Callbacks *sensor_callbacks; + unsigned num_sensor_callbacks; + bool ensure_enough_sensor_callbacks(unsigned num); + + // Callbacks that are one per tracker + vrpn_Callback_List d_tracker2roomchange_list; + vrpn_Callback_List d_workspacechange_list; + + static int VRPN_CALLBACK + handle_change_message(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_vel_change_message(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_acc_change_message(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_tracker2room_change_message(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_unit2sensor_change_message(void *userdata, vrpn_HANDLERPARAM p); + static int VRPN_CALLBACK + handle_workspace_change_message(void *userdata, vrpn_HANDLERPARAM p); +}; + +// End of vrpn_TRACKER_H +#endif diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Types.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Types.h new file mode 100644 index 000000000000..54cfa42c27ef --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_Types.h @@ -0,0 +1,223 @@ +#ifndef VRPN_TYPES_H +#define VRPN_TYPES_H + +#include "vrpn_Configure.h" + +//------------------------------------------------------------------ +// Do a test for a C++ compiler first, to ensure it's the first +// error message. Otherwise, the error messages you get are +// completely cryptic. +//------------------------------------------------------------------ +#ifndef __cplusplus +#ifndef VRPN_IGNORE_NO_CPLUSPLUS +#error Need to compile with a C++ compiler, not a C compiler. The problem is that in Windows, filenames are case-insensitive. So the compiler cannot tell mumble.c from mumble.C. Visual Studio decided to make .cpp (which used to mean run the C preprocessor) mean C++ and both .c and .C mean C. The other problem is that when you insert a new file into a project, it FOR THAT FILE makes an override. The project settings say C++ but if you right-click on the file itself it has an override to compile with C. This needs to be changed for both the .C file and the .h file. +#endif +#endif + +//------------------------------------------------------------------ +// This section contains definitions for architecture-dependent +// types. It is important that the data sent over a vrpn_Connection +// be of the same size on all hosts sending and receiving it. Since +// C++ does not constrain the size of 'int', 'long', 'double' and +// so forth, we create new types here that are defined correctly for +// each architecture and use them for all data that might be sent +// across a connection. +// Part of porting VRPN to a new architecture is defining the +// types below on that architecture in such as way that the compiler +// can determine which machine type it is on. +//------------------------------------------------------------------ + +#undef VRPN_ARCH + +#ifdef sgi +#define VRPN_ARCH sgi +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +#ifdef hpux +#define VRPN_ARCH hpux +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +// For PixelFlow aCC compiler +#ifdef __hpux +#undef VRPN_ARCH +#define VRPN_ARCH __hpux +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +#ifdef sparc +#define VRPN_ARCH sparc +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +#ifdef linux +#define VRPN_ARCH linux +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +#ifdef _AIX +#define VRPN_ARCH aix +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +// _WIN32 is defined for all compilers for Windows (cygnus g++ included) +// WIN32 (sans underline) is defined only by the Windows VC++ compiler. +// +// DO NOT EVER USE WIN32 +// +// It is too hard to differentiate from _WIN32, and may not actually be +// defined by VC++ (it's a project option). If you use WIN32 to distinguish +// between VC++ and cygwin/g++, may your wrists quickly develop a nerve +// disorder that prevents you from ever typing again ;) +// +#ifdef _WIN32 +#define VRPN_ARCH _WIN32 +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +#if defined(FreeBSD) || defined(__FreeBSD__) +#ifndef FreeBSD +#define FreeBSD +#endif +#define VRPN_ARCH FreeBSD +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +#ifdef __APPLE__ +#define VRPN_ARCH MacOSX +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif + +// Architecture of last resort. +#ifndef VRPN_ARCH +#ifdef __GNUC__ +#define VRPN_ARCH _WIN32 +typedef char vrpn_int8; +typedef unsigned char vrpn_uint8; +typedef short vrpn_int16; +typedef unsigned short vrpn_uint16; +typedef int vrpn_int32; +typedef unsigned int vrpn_uint32; +typedef float vrpn_float32; +typedef double vrpn_float64; +#endif +#endif + +#ifndef VRPN_ARCH +#error Need to define architecture-dependent sizes in this file +#endif + +// Prevent use of this macro outside this file; +// if you need to distinguish more types, then define new types in this file. + +#undef VRPN_ARCH + +// ******************************************************* +// you should NOT need to modify anything below this point +// ******************************************************* +#ifdef __cplusplus +typedef vrpn_int16 vrpn_bool; + +const vrpn_int16 vrpn_true = 1; +const vrpn_int16 vrpn_false = 0; +const vrpn_int16 vrpn_TRUE = 1; +const vrpn_int16 vrpn_FALSE = 0; +const vrpn_int16 VRPN_TRUE = 1; +const vrpn_int16 VRPN_FALSE = 0; +#endif + +// should we add a success & fail? + +// [juliano 10/9/99] The vrpn bool variables can not actually be fully +// optimized away, because the compiler is not allowed to assume their +// values don't change. +// +// [juliano 11/28/99] Perhaps the optimization can be done if they are +// static? I don't know enough about what compilers can/cannot do today. +// +// If you are willing to assume templates, there is an alternative using +// a traits class that does make the optimization possible (and likely). +// +// If you don't want to use templates, but still want the sizeof +// these things be vrpn_int16, you can use macros like this. +// +// #define vrpn_false /*false*/vrpn_int16(0) +// #define vrpn_true /*true*/vrpn_int16(1) +// +// With this method, you will still be able to tell, in the +// compiler error messages, what the real code contains. +// +// If you don't care about them being a different type than +// vrpn_int16 (probably not a good idea), you can use this technique, +// which guarantees optimizations can be performed. +// +// enum vrpn_bool_constants_t{ +// vrpn_false=0, vrpn_FALSE=0, VRPN_FALSE=0, +// vrpn_true=1, vrpn_TRUE=1, VRPN_TRUE=1 }; +// + +#endif // VRPN_TYPES_H diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_WindowsH.h b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_WindowsH.h new file mode 100644 index 000000000000..a28d3d3c6b4d --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/Include/vrpn/vrpn_WindowsH.h @@ -0,0 +1,78 @@ +/** @file + @brief Header to minimally include windows.h + + @date 2015 + + @author + Ryan Pavlik + Sensics, Inc. + +*/ + + +// Copyright 2015 Sensics, Inc. +// Distributed under the Boost Software License, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#ifndef INCLUDED_vrpn_WindowsH_h_GUID_97C90BFD_D6C3_4AB3_3272_A10F7448D165 +#define INCLUDED_vrpn_WindowsH_h_GUID_97C90BFD_D6C3_4AB3_3272_A10F7448D165 + +#ifdef _WIN32 + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#define VRPN_WIN32_LEAN_AND_MEAN +#endif + +#ifndef NOMINMAX +#define NOMINMAX +#define VRPN_NOMINMAX +#endif + +#ifndef NOSERVICE +#define NOSERVICE +#define VRPN_NOSERVICE +#endif + +#ifndef NOMCX +#define NOMCX +#define VRPN_NOMCX +#endif + +#ifndef NOIME +#define NOIME +#define VRPN_NOIME +#endif + +#include + +#ifdef VRPN_WIN32_LEAN_AND_MEAN +#undef VRPN_WIN32_LEAN_AND_MEAN +#undef WIN32_LEAN_AND_MEAN +#endif + +#ifdef VRPN_NOMINMAX +#undef VRPN_NOMINMAX +#undef NOMINMAX +#endif + +#ifdef VRPN_NOSERVICE +#undef VRPN_NOSERVICE +#undef NOSERVICE +#endif + +#ifdef VRPN_NOMCX +#undef VRPN_NOMCX +#undef NOMCX +#endif + +#ifdef VRPN_NOIME +#undef VRPN_NOIME +#undef NOIME +#endif + +#endif // _WIN32 + +#endif // INCLUDED_vrpn_WindowsH_h_GUID_97C90BFD_D6C3_4AB3_3272_A10F7448D165 + diff --git a/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/VRPN.tps b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/VRPN.tps new file mode 100644 index 000000000000..83f237757480 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/ThirdParty/Vrpn/VRPN.tps @@ -0,0 +1,13 @@ + + + VRPN + /Enterprise/Plugins/Runtime/vrCluster/ThirdParty/Vrpn + Used by the vrCluster plugin for Enterprise. + https://github.com/vrpn/vrpn/wiki/License + + Licensees + Git + P4 + + /Engine/Source/ThirdParty/Licenses/VRPN_License.txt + \ No newline at end of file diff --git a/Engine/Plugins/Runtime/nDisplay/nDisplay.uplugin b/Engine/Plugins/Runtime/nDisplay/nDisplay.uplugin new file mode 100644 index 000000000000..42a41a54eb14 --- /dev/null +++ b/Engine/Plugins/Runtime/nDisplay/nDisplay.uplugin @@ -0,0 +1,39 @@ +{ + "FileVersion": 3, + "Version" : 1, + "VersionName" : "1.0", + "FriendlyName": "nDisplay", + "CreatedBy" : "Epic Games Inc", + "CreatedByURL" : "http://epicgames.com", + "EngineVersion" : "4.20.0", + "Description": "Mono/Stereo standalone/cluster virtual reality", + "Category": "Misc", + "DocsURL": "", + "MarketplaceURL": "", + "SupportURL": "", + "EnabledByDefault": false, + "CanContainContent": true, + "IsBetaVersion": true, + "Installed": false, + "Modules": [ + { + "Name": "DisplayCluster", + "Type": "Runtime", + "LoadingPhase": "Default", + "WhitelistPlatforms" : + [ + "Win64" + ] + }, + { + "Name": "DisplayClusterEditor", + "Type": "Editor", + "LoadingPhase": "Default", + "WhitelistPlatforms" : + [ + "Win64" + ] + } + + ] +} diff --git a/Engine/Source/Developer/MeshDescriptionOperations/Private/LayoutUV.cpp b/Engine/Source/Developer/MeshDescriptionOperations/Private/LayoutUV.cpp index 053659ede567..adc7eefdb288 100644 --- a/Engine/Source/Developer/MeshDescriptionOperations/Private/LayoutUV.cpp +++ b/Engine/Source/Developer/MeshDescriptionOperations/Private/LayoutUV.cpp @@ -29,22 +29,36 @@ namespace MeshDescriptionOp const float ThreshUVsAreSame = GetUVEqualityThreshold(); double Begin = FPlatformTime::Seconds(); - uint32 NumIndexes = MeshDescription->VertexInstances().Num(); - uint32 NumTris = NumIndexes / 3; - check(MeshDescription->Polygons().Num() == NumTris); + uint32 NumTris = 0; + for (const FPolygonID& PolygonID : MeshDescription->Polygons().GetElementIDs()) + { + NumTris += MeshDescription->GetPolygonTriangles(PolygonID).Num(); + } + uint32 NumIndexes = NumTris * 3; TArray< int32 > TranslatedMatches; TranslatedMatches.SetNumUninitialized(NumIndexes); TexCoords.SetNumUninitialized(NumIndexes); - int32 VertexInstanceIndex = 0; + int32 WedgeIndex = 0; + RemapVerts.SetNumUninitialized(NumIndexes); const TVertexInstanceAttributeArray& VertexUVs = MeshDescription->VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::TextureCoordinate, SrcChannel); - for (const FVertexInstanceID VertexInstanceID : MeshDescription->VertexInstances().GetElementIDs()) + + for (const FPolygonID& PolygonID : MeshDescription->Polygons().GetElementIDs()) { - check(VertexInstanceIndex == VertexInstanceID.GetValue()); - TranslatedMatches[VertexInstanceIndex] = -1; - TexCoords[VertexInstanceIndex] = VertexUVs[VertexInstanceID]; - VertexInstanceIndex++; + const TArray& Triangles = MeshDescription->GetPolygonTriangles(PolygonID); + for (const FMeshTriangle& MeshTriangle : Triangles) + { + for (int32 Corner = 0; Corner < 3; ++Corner) + { + const FVertexInstanceID VertexInstanceID = MeshTriangle.GetVertexInstanceID(Corner); + + TranslatedMatches[WedgeIndex] = -1; + TexCoords[WedgeIndex] = VertexUVs[VertexInstanceID]; + RemapVerts[WedgeIndex] = VertexInstanceID.GetValue(); + ++WedgeIndex; + } + } } // Build disjoint set @@ -191,7 +205,7 @@ namespace MeshDescriptionOp { uint32 Index = 3 * SortedTris[Tri] + k; - FVertexInstanceID VertexInstanceID(Index); + FVertexInstanceID VertexInstanceID(RemapVerts[Index]); Positions[k] = VertexPositions[MeshDescription->GetVertexInstanceVertex(VertexInstanceID)]; UVs[k] = TexCoords[Index]; @@ -1079,10 +1093,10 @@ namespace MeshDescriptionOp { uint32 Index = 3 * SortedTris[Tri] + k; const FVector2D& UV = TexCoords[Index]; - const FVertexInstanceID VertexInstanceID(Index); + const FVertexInstanceID VertexInstanceID(RemapVerts[Index]); VertexUVs[VertexInstanceID] = UV.X * Chart.PackingScaleU + UV.Y * Chart.PackingScaleV + Chart.PackingBias; } } } } -} \ No newline at end of file +} diff --git a/Engine/Source/Developer/MeshDescriptionOperations/Private/LayoutUV.h b/Engine/Source/Developer/MeshDescriptionOperations/Private/LayoutUV.h index 757374253136..e341c5ebe42e 100644 --- a/Engine/Source/Developer/MeshDescriptionOperations/Private/LayoutUV.h +++ b/Engine/Source/Developer/MeshDescriptionOperations/Private/LayoutUV.h @@ -81,6 +81,7 @@ namespace MeshDescriptionOp TArray< FMeshChart > Charts; float TotalUVArea; float MaxChartSize; + TArray< int32 > RemapVerts; FAllocator2D LayoutRaster; FAllocator2D ChartRaster; @@ -93,8 +94,8 @@ namespace MeshDescriptionOp inline bool FLayoutUV::PositionsMatch(uint32 a, uint32 b) const { - const FVertexInstanceID VertexInstanceIDA(a); - const FVertexInstanceID VertexInstanceIDB(b); + const FVertexInstanceID VertexInstanceIDA(RemapVerts[a]); + const FVertexInstanceID VertexInstanceIDB(RemapVerts[b]); const FVertexID VertexIDA = MeshDescription->GetVertexInstanceVertex(VertexInstanceIDA); const FVertexID VertexIDB = MeshDescription->GetVertexInstanceVertex(VertexInstanceIDB); @@ -113,8 +114,8 @@ namespace MeshDescriptionOp return true; } - const FVertexInstanceID VertexInstanceIDA(a); - const FVertexInstanceID VertexInstanceIDB(b); + const FVertexInstanceID VertexInstanceIDA(RemapVerts[a]); + const FVertexInstanceID VertexInstanceIDB(RemapVerts[b]); const TVertexInstanceAttributeArray& VertexNormals = MeshDescription->VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Normal); return VertexNormals[VertexInstanceIDA].Equals(VertexNormals[VertexInstanceIDB], THRESH_NORMALS_ARE_SAME); @@ -130,8 +131,8 @@ namespace MeshDescriptionOp return true; } - const FVertexInstanceID VertexInstanceIDA(a); - const FVertexInstanceID VertexInstanceIDB(b); + const FVertexInstanceID VertexInstanceIDA(RemapVerts[a]); + const FVertexInstanceID VertexInstanceIDB(RemapVerts[b]); const TVertexInstanceAttributeArray& VertexUVs = MeshDescription->VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::TextureCoordinate, SrcChannel); return VertexUVs[VertexInstanceIDA].Equals(VertexUVs[VertexInstanceIDB], GetUVEqualityThreshold()); @@ -150,7 +151,7 @@ namespace MeshDescriptionOp FVector2D UVs[3]; for (int k = 0; k < 3; k++) { - UVs[k] = VertexUVs[FVertexInstanceID((3 * Tri) + k)]; + UVs[k] = VertexUVs[FVertexInstanceID(RemapVerts[(3 * Tri) + k])]; } FVector2D EdgeUV1 = UVs[1] - UVs[0]; diff --git a/Engine/Source/Developer/MeshDescriptionOperations/Private/MeshDescriptionOperations.cpp b/Engine/Source/Developer/MeshDescriptionOperations/Private/MeshDescriptionOperations.cpp index cbb051f0901c..1df09d1bbb1d 100644 --- a/Engine/Source/Developer/MeshDescriptionOperations/Private/MeshDescriptionOperations.cpp +++ b/Engine/Source/Developer/MeshDescriptionOperations/Private/MeshDescriptionOperations.cpp @@ -65,9 +65,9 @@ namespace MeshDescriptionOperationNamespace void FMeshDescriptionOperations::ConvertHardEdgesToSmoothGroup(const UMeshDescription* SourceMeshDescription, struct FRawMesh &DestinationRawMesh) { TMap PolygonSmoothGroup; - PolygonSmoothGroup.Reserve(SourceMeshDescription->Polygons().Num()); + PolygonSmoothGroup.Reserve(SourceMeshDescription->Polygons().GetArraySize()); TArray ConsumedPolygons; - ConsumedPolygons.AddZeroed(SourceMeshDescription->Polygons().Num()); + ConsumedPolygons.AddZeroed(SourceMeshDescription->Polygons().GetArraySize()); TMap < FPolygonID, uint32> PolygonAvoidances; @@ -256,21 +256,14 @@ void FMeshDescriptionOperations::ConverToRawMesh(const UMeshDescription* SourceM const TPolygonGroupAttributeArray& PolygonGroupMaterialSlotName = SourceMeshDescription->PolygonGroupAttributes().GetAttributes(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); DestinationRawMesh.VertexPositions.AddZeroed(SourceMeshDescription->Vertices().Num()); + TArray RemapVerts; + RemapVerts.AddZeroed(SourceMeshDescription->Vertices().GetArraySize()); + int32 VertexIndex = 0; for (const FVertexID& VertexID : SourceMeshDescription->Vertices().GetElementIDs()) { - int32 VertexIDValue = VertexID.GetValue(); - DestinationRawMesh.VertexPositions[VertexIDValue] = VertexPositions[VertexID]; - } - int32 VertexInstanceNumber = SourceMeshDescription->VertexInstances().Num(); - DestinationRawMesh.WedgeColors.AddZeroed(VertexInstanceNumber); - DestinationRawMesh.WedgeIndices.AddZeroed(VertexInstanceNumber); - DestinationRawMesh.WedgeTangentX.AddZeroed(VertexInstanceNumber); - DestinationRawMesh.WedgeTangentY.AddZeroed(VertexInstanceNumber); - DestinationRawMesh.WedgeTangentZ.AddZeroed(VertexInstanceNumber); - int32 ExistingUVCount = VertexInstanceUVs.GetNumIndices(); - for (int32 UVIndex = 0; UVIndex < ExistingUVCount; ++UVIndex) - { - DestinationRawMesh.WedgeTexCoords[UVIndex].AddZeroed(VertexInstanceNumber); + DestinationRawMesh.VertexPositions[VertexIndex] = VertexPositions[VertexID]; + RemapVerts[VertexID.GetValue()] = VertexIndex; + ++VertexIndex; } int32 TriangleNumber = 0; @@ -281,7 +274,20 @@ void FMeshDescriptionOperations::ConverToRawMesh(const UMeshDescription* SourceM DestinationRawMesh.FaceMaterialIndices.AddZeroed(TriangleNumber); DestinationRawMesh.FaceSmoothingMasks.AddZeroed(TriangleNumber); + int32 WedgeIndexNumber = TriangleNumber * 3; + DestinationRawMesh.WedgeColors.AddZeroed(WedgeIndexNumber); + DestinationRawMesh.WedgeIndices.AddZeroed(WedgeIndexNumber); + DestinationRawMesh.WedgeTangentX.AddZeroed(WedgeIndexNumber); + DestinationRawMesh.WedgeTangentY.AddZeroed(WedgeIndexNumber); + DestinationRawMesh.WedgeTangentZ.AddZeroed(WedgeIndexNumber); + int32 ExistingUVCount = VertexInstanceUVs.GetNumIndices(); + for (int32 UVIndex = 0; UVIndex < ExistingUVCount; ++UVIndex) + { + DestinationRawMesh.WedgeTexCoords[UVIndex].AddZeroed(WedgeIndexNumber); + } + int32 TriangleIndex = 0; + int32 WedgeIndex = 0; for (const FPolygonID& PolygonID : SourceMeshDescription->Polygons().GetElementIDs()) { const FPolygonGroupID& PolygonGroupID = SourceMeshDescription->GetPolygonPolygonGroup(PolygonID); @@ -301,16 +307,17 @@ void FMeshDescriptionOperations::ConverToRawMesh(const UMeshDescription* SourceM for (int32 Corner = 0; Corner < 3; ++Corner) { const FVertexInstanceID VertexInstanceID = MeshTriangle.GetVertexInstanceID(Corner); - const int32 VertexInstanceIDValue = VertexInstanceID.GetValue(); - DestinationRawMesh.WedgeColors[VertexInstanceIDValue] = FLinearColor(VertexInstanceColors[VertexInstanceID]).ToFColor(true); - DestinationRawMesh.WedgeIndices[VertexInstanceIDValue] = SourceMeshDescription->GetVertexInstanceVertex(VertexInstanceID).GetValue(); - DestinationRawMesh.WedgeTangentX[VertexInstanceIDValue] = VertexInstanceTangents[VertexInstanceID]; - DestinationRawMesh.WedgeTangentY[VertexInstanceIDValue] = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID]; - DestinationRawMesh.WedgeTangentZ[VertexInstanceIDValue] = VertexInstanceNormals[VertexInstanceID]; + + DestinationRawMesh.WedgeColors[WedgeIndex] = FLinearColor(VertexInstanceColors[VertexInstanceID]).ToFColor(true); + DestinationRawMesh.WedgeIndices[WedgeIndex] = RemapVerts[SourceMeshDescription->GetVertexInstanceVertex(VertexInstanceID).GetValue()]; + DestinationRawMesh.WedgeTangentX[WedgeIndex] = VertexInstanceTangents[VertexInstanceID]; + DestinationRawMesh.WedgeTangentY[WedgeIndex] = FVector::CrossProduct(VertexInstanceNormals[VertexInstanceID], VertexInstanceTangents[VertexInstanceID]).GetSafeNormal() * VertexInstanceBinormalSigns[VertexInstanceID]; + DestinationRawMesh.WedgeTangentZ[WedgeIndex] = VertexInstanceNormals[VertexInstanceID]; for (int32 UVIndex = 0; UVIndex < ExistingUVCount; ++UVIndex) { - DestinationRawMesh.WedgeTexCoords[UVIndex][VertexInstanceIDValue] = VertexInstanceUVs.GetArrayForIndex(UVIndex)[VertexInstanceID]; + DestinationRawMesh.WedgeTexCoords[UVIndex][WedgeIndex] = VertexInstanceUVs.GetArrayForIndex(UVIndex)[VertexInstanceID]; } + ++WedgeIndex; } ++TriangleIndex; } @@ -752,9 +759,6 @@ void FMeshDescriptionOperations::CreateNormals(UMeshDescription* MeshDescription continue; } - //Make sure we consume all our vertex instance - check(VertexInfoMap.Num() == MeshDescription->GetVertexVertexInstances(VertexID).Num()); - //Build all group by recursively traverse all polygon connected to the vertex TArray> Groups; TArray ConsumedPolygon; diff --git a/Engine/Source/Developer/MeshSimplifier/Private/QuadricMeshReduction.cpp b/Engine/Source/Developer/MeshSimplifier/Private/QuadricMeshReduction.cpp index 6d631ba5e8de..8825d86c99ea 100644 --- a/Engine/Source/Developer/MeshSimplifier/Private/QuadricMeshReduction.cpp +++ b/Engine/Source/Developer/MeshSimplifier/Private/QuadricMeshReduction.cpp @@ -524,9 +524,12 @@ public: TMap< int32, int32 > VertsMap; TArray DupVerts; - int32 NumWedges = InMesh->VertexInstances().Num(); - int32 NumFaces = NumWedges / 3; - check(InMesh->Polygons().Num() == NumFaces); + int32 NumFaces = 0; + for (const FPolygonID& PolygonID : InMesh->Polygons().GetElementIDs()) + { + NumFaces += InMesh->GetPolygonTriangles(PolygonID).Num(); + } + int32 NumWedges = NumFaces * 3; const TVertexAttributeArray& InVertexPositions = InMesh->VertexAttributes().GetAttributes(MeshAttribute::Vertex::Position); const TVertexInstanceAttributeArray& InVertexNormals = InMesh->VertexInstanceAttributes().GetAttributes(MeshAttribute::VertexInstance::Normal); @@ -538,118 +541,124 @@ public: const TPolygonGroupAttributeArray& InPolygonGroupMaterialNames = InMesh->PolygonGroupAttributes().GetAttributes(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); TPolygonGroupAttributeArray& OutPolygonGroupMaterialNames = OutReducedMesh->PolygonGroupAttributes().GetAttributes(MeshAttribute::PolygonGroup::ImportedMaterialSlotName); - // Process each face, build vertex buffer and index buffer - for (int32 FaceIndex = 0; FaceIndex < NumFaces; FaceIndex++) + int32 FaceIndex = 0; + for (const FPolygonID& PolygonID : InMesh->Polygons().GetElementIDs()) { + const TArray& Triangles = InMesh->GetPolygonTriangles(PolygonID); + FVertexInstanceID VertexInstanceIDs[3]; FVertexID VertexIDs[3]; - FVector Positions[3]; - for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++) + + for (const FMeshTriangle& MeshTriangle : Triangles) { - VertexInstanceIDs[CornerIndex] = FVertexInstanceID((FaceIndex * 3) + CornerIndex); - VertexIDs[CornerIndex] = InMesh->GetVertexInstanceVertex(VertexInstanceIDs[CornerIndex]); - Positions[CornerIndex] = InVertexPositions[VertexIDs[CornerIndex]]; - } - - // Don't process degenerate triangles. - if (PointsEqual(Positions[0], Positions[1]) || - PointsEqual(Positions[0], Positions[2]) || - PointsEqual(Positions[1], Positions[2])) - { - continue; - } - - int32 VertexIndices[3]; - for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++) - { - int32 WedgeIndex = FaceIndex * 3 + CornerIndex; - - TVertSimp< NumTexCoords > NewVert; - - const TArray& VertexInstanceConnectedPolygons = InMesh->GetVertexInstanceConnectedPolygons(VertexInstanceIDs[CornerIndex]); - if (VertexInstanceConnectedPolygons.Num() > 0) + for (int32 CornerIndex = 0; CornerIndex < 3; ++CornerIndex) { - const FPolygonID PolygonID = VertexInstanceConnectedPolygons[0]; - NewVert.MaterialIndex = InMesh->GetPolygonPolygonGroup(PolygonID).GetValue(); - // @todo: check with Alexis: OK to conflate material index with polygon group ID? (what if there are gaps in the polygon group array?) + VertexInstanceIDs[CornerIndex] = MeshTriangle.GetVertexInstanceID(CornerIndex); + VertexIDs[CornerIndex] = InMesh->GetVertexInstanceVertex(VertexInstanceIDs[CornerIndex]); + Positions[CornerIndex] = InVertexPositions[VertexIDs[CornerIndex]]; } - NewVert.Position = Positions[CornerIndex]; - NewVert.Tangents[0] = InVertexTangents[VertexInstanceIDs[CornerIndex]]; - NewVert.Normal = InVertexNormals[VertexInstanceIDs[CornerIndex]]; - NewVert.Tangents[1] = FVector(0.0f); - if (!NewVert.Normal.IsNearlyZero(SMALL_NUMBER) && !NewVert.Tangents[0].IsNearlyZero(SMALL_NUMBER)) + // Don't process degenerate triangles. + if (PointsEqual(Positions[0], Positions[1]) || + PointsEqual(Positions[0], Positions[2]) || + PointsEqual(Positions[1], Positions[2])) { - NewVert.Tangents[1] = FVector::CrossProduct(NewVert.Normal, NewVert.Tangents[0]).GetSafeNormal() * InVertexBinormalSigns[VertexInstanceIDs[CornerIndex]]; + continue; } - // Fix bad tangents - NewVert.Tangents[0] = NewVert.Tangents[0].ContainsNaN() ? FVector::ZeroVector : NewVert.Tangents[0]; - NewVert.Tangents[1] = NewVert.Tangents[1].ContainsNaN() ? FVector::ZeroVector : NewVert.Tangents[1]; - NewVert.Normal = NewVert.Normal.ContainsNaN() ? FVector::ZeroVector : NewVert.Normal; - NewVert.Color = FLinearColor(InVertexColors[VertexInstanceIDs[CornerIndex]]); - - for (int32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++) + int32 VertexIndices[3]; + for (int32 CornerIndex = 0; CornerIndex < 3; CornerIndex++) { - if (UVIndex < InVertexUVs.GetNumIndices()) - { - NewVert.TexCoords[UVIndex] = InVertexUVs.GetArrayForIndex(UVIndex)[VertexInstanceIDs[CornerIndex]]; - InMeshNumTexCoords = FMath::Max(UVIndex+1, InMeshNumTexCoords); - } - else - { - NewVert.TexCoords[UVIndex] = FVector2D::ZeroVector; - } - } + int32 WedgeIndex = FaceIndex * 3 + CornerIndex; + ++FaceIndex; - // Make sure this vertex is valid from the start - NewVert.Correct(); + TVertSimp< NumTexCoords > NewVert; - DupVerts.Reset(); - InOverlappingCorners.MultiFind(WedgeIndex, DupVerts); - DupVerts.Sort(); - - int32 Index = INDEX_NONE; - for (int32 k = 0; k < DupVerts.Num(); k++) - { - if (DupVerts[k] >= WedgeIndex) + const TArray& VertexInstanceConnectedPolygons = InMesh->GetVertexInstanceConnectedPolygons(VertexInstanceIDs[CornerIndex]); + if (VertexInstanceConnectedPolygons.Num() > 0) { - // the verts beyond me haven't been placed yet, so these duplicates are not relevant - break; + const FPolygonID ConnectedPolygonID = VertexInstanceConnectedPolygons[0]; + NewVert.MaterialIndex = InMesh->GetPolygonPolygonGroup(ConnectedPolygonID).GetValue(); + // @todo: check with Alexis: OK to conflate material index with polygon group ID? (what if there are gaps in the polygon group array?) } - int32* Location = VertsMap.Find(DupVerts[k]); - if (Location) + NewVert.Position = Positions[CornerIndex]; + NewVert.Tangents[0] = InVertexTangents[VertexInstanceIDs[CornerIndex]]; + NewVert.Normal = InVertexNormals[VertexInstanceIDs[CornerIndex]]; + NewVert.Tangents[1] = FVector(0.0f); + if (!NewVert.Normal.IsNearlyZero(SMALL_NUMBER) && !NewVert.Tangents[0].IsNearlyZero(SMALL_NUMBER)) { - TVertSimp< NumTexCoords >& FoundVert = Verts[*Location]; + NewVert.Tangents[1] = FVector::CrossProduct(NewVert.Normal, NewVert.Tangents[0]).GetSafeNormal() * InVertexBinormalSigns[VertexInstanceIDs[CornerIndex]]; + } - if (NewVert.Equals(FoundVert)) + // Fix bad tangents + NewVert.Tangents[0] = NewVert.Tangents[0].ContainsNaN() ? FVector::ZeroVector : NewVert.Tangents[0]; + NewVert.Tangents[1] = NewVert.Tangents[1].ContainsNaN() ? FVector::ZeroVector : NewVert.Tangents[1]; + NewVert.Normal = NewVert.Normal.ContainsNaN() ? FVector::ZeroVector : NewVert.Normal; + NewVert.Color = FLinearColor(InVertexColors[VertexInstanceIDs[CornerIndex]]); + + for (int32 UVIndex = 0; UVIndex < NumTexCoords; UVIndex++) + { + if (UVIndex < InVertexUVs.GetNumIndices()) { - Index = *Location; - break; + NewVert.TexCoords[UVIndex] = InVertexUVs.GetArrayForIndex(UVIndex)[VertexInstanceIDs[CornerIndex]]; + InMeshNumTexCoords = FMath::Max(UVIndex+1, InMeshNumTexCoords); + } + else + { + NewVert.TexCoords[UVIndex] = FVector2D::ZeroVector; } } + + // Make sure this vertex is valid from the start + NewVert.Correct(); + + DupVerts.Reset(); + InOverlappingCorners.MultiFind(WedgeIndex, DupVerts); + DupVerts.Sort(); + + int32 Index = INDEX_NONE; + for (int32 k = 0; k < DupVerts.Num(); k++) + { + if (DupVerts[k] >= WedgeIndex) + { + // the verts beyond me haven't been placed yet, so these duplicates are not relevant + break; + } + + int32* Location = VertsMap.Find(DupVerts[k]); + if (Location) + { + TVertSimp< NumTexCoords >& FoundVert = Verts[*Location]; + + if (NewVert.Equals(FoundVert)) + { + Index = *Location; + break; + } + } + } + if (Index == INDEX_NONE) + { + Index = Verts.Add(NewVert); + VertsMap.Add(WedgeIndex, Index); + } + VertexIndices[CornerIndex] = Index; } - if (Index == INDEX_NONE) + + // Reject degenerate triangles. + if (VertexIndices[0] == VertexIndices[1] || + VertexIndices[1] == VertexIndices[2] || + VertexIndices[0] == VertexIndices[2]) { - Index = Verts.Add(NewVert); - VertsMap.Add(WedgeIndex, Index); + continue; } - VertexIndices[CornerIndex] = Index; - } - // Reject degenerate triangles. - if (VertexIndices[0] == VertexIndices[1] || - VertexIndices[1] == VertexIndices[2] || - VertexIndices[0] == VertexIndices[2]) - { - continue; + Indexes.Add(VertexIndices[0]); + Indexes.Add(VertexIndices[1]); + Indexes.Add(VertexIndices[2]); } - - Indexes.Add(VertexIndices[0]); - Indexes.Add(VertexIndices[1]); - Indexes.Add(VertexIndices[2]); } uint32 NumVerts = Verts.Num(); @@ -792,7 +801,13 @@ public: { for (int32 Index = 0; Index < AttributeIndicesArray.GetNumIndices(); ++Index) { - OutReducedMesh->PolygonGroupAttributes().SetAttribute(MaterialPolygonGroupID, Name, Index, AttributeIndicesArray.GetArrayForIndex(Index)[PolygonGroupID]); + // Only copy shared attribute values, since input mesh description can differ from output mesh description + const auto& Value = AttributeIndicesArray.GetArrayForIndex(Index)[PolygonGroupID]; + using AttributeType = typename TDecay::Type; + if (OutReducedMesh->PolygonGroupAttributes().HasAttribute(Name)) + { + OutReducedMesh->PolygonGroupAttributes().SetAttribute(MaterialPolygonGroupID, Name, Index, Value); + } } } ); diff --git a/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp b/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp index 035be6431519..cbb0c466cff3 100644 --- a/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp +++ b/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp @@ -39,6 +39,7 @@ #include "SAssetView.h" #include "ContentBrowserModule.h" #include "Dialogs/Dialogs.h" +#include "SMetaDataView.h" #include "ObjectTools.h" #include "PackageTools.h" @@ -513,8 +514,8 @@ void FAssetContextMenu::MakeAssetActionsSubMenu(FMenuBuilder& MenuBuilder) // Create Metadata menu MenuBuilder.AddMenuEntry( - LOCTEXT("ShowAssetMetaData", "Show Meta-data"), - LOCTEXT("ShowAssetMetaDataTooltip", "Show the asset meta-data dialog."), + LOCTEXT("ShowAssetMetaData", "Show Metadata"), + LOCTEXT("ShowAssetMetaDataTooltip", "Show the asset metadata dialog."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteShowAssetMetaData), @@ -1781,47 +1782,36 @@ void FAssetContextMenu::ExecutePropertyMatrix() void FAssetContextMenu::ExecuteShowAssetMetaData() { - bool bFirstAsset = true; - FString OutputValue; - for (const FAssetData& AssetData : SelectedAssets) { UObject* Asset = AssetData.GetAsset(); if (Asset) { - if (!bFirstAsset) - { - OutputValue.Append(TEXT("\n\n")); - } - bFirstAsset = false; - - OutputValue.Append(*Asset->GetPathName()); - TMap* TagValues = UMetaData::GetMapForObject(Asset); if (TagValues) { - for (TMap::TIterator It(*TagValues); It; ++It) - { - OutputValue.Append(TEXT("\n ")); - OutputValue.Append(It.Key().ToString()); - OutputValue.Append(TEXT(": ")); - OutputValue.Append(It.Value()); - } - } - else - { - OutputValue.Append(TEXT("\n ")); - OutputValue.Append(NSLOCTEXT("AssetContextMenu", "ShowMetaData", "No meta-data found.").ToString()); + // Create and display a resizable window to display the MetaDataView for each asset with metadata + FString Title = FString::Printf(TEXT("Metadata: %s"), *AssetData.AssetName.ToString()); + + TSharedPtr< SWindow > Window = SNew(SWindow) + .Title(FText::FromString(Title)) + .SupportsMaximize(false) + .SupportsMinimize(false) + .MinWidth(500.0f) + .MinHeight(250.0f) + [ + SNew(SBorder) + .Padding(4.f) + .BorderImage(FEditorStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SMetaDataView, *TagValues) + ] + ]; + + FSlateApplication::Get().AddWindow(Window.ToSharedRef()); } } } - - SGenericDialogWidget::OpenDialog( - NSLOCTEXT("AssetContextMenu", "ShowMetaData", "Show Meta-data"), - SNew(SMultiLineEditableTextBox) - .Text(FText::FromString(OutputValue)) - .IsReadOnly(true) - ); } void FAssetContextMenu::ExecuteEditAsset() diff --git a/Engine/Source/Editor/ContentBrowser/Private/SMetaDataView.cpp b/Engine/Source/Editor/ContentBrowser/Private/SMetaDataView.cpp new file mode 100644 index 000000000000..e31c652f442a --- /dev/null +++ b/Engine/Source/Editor/ContentBrowser/Private/SMetaDataView.cpp @@ -0,0 +1,148 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "SMetaDataView.h" + +#include "Widgets/Text/SMultiLineEditableText.h" +#include "Widgets/Views/SListView.h" +#include "Widgets/Views/STableRow.h" + +namespace MetaDataViewColumns +{ + /** IDs for list columns */ + static const FName ColumnID_Tag("Tag"); + static const FName ColumnID_Value("Value"); +} + +struct FMetaDataLine +{ + FMetaDataLine(FName InTag, const FString& InValue) + : Tag(InTag) + , Value(InValue) + { + } + + FName Tag; + FString Value; +}; + +/** + * The widget that represents a row in the MetaDataView's list view widget. Generates a widget for each column, on-demand. + */ +class SMetaDataViewRow : public SMultiColumnTableRow< TSharedPtr< FMetaDataLine > > +{ + +public: + + SLATE_BEGIN_ARGS(SMetaDataViewRow) {} + SLATE_END_ARGS() + + /** + * Construct this widget. Called by the SNew() Slate macro. + * + * @param InArgs Declaration used by the SNew() macro to construct this widget + * @param InMetaData The metadata tag/value to display in the row widget + * @param InOwnerTableView The owner of the row widget + */ + void Construct(const FArguments& InArgs, TSharedRef< FMetaDataLine > InMetaData, TSharedRef< STableViewBase > InOwnerTableView); + + /** + * Constructs the widget that represents the specified ColumnID for this Row + * + * @param ColumnID A unique ID for a column in this TableView; see SHeaderRow::FColumn for more info. + * + * @return a widget to represent the contents of a cell in this row of a TableView. + */ + virtual TSharedRef< SWidget > GenerateWidgetForColumn(const FName& ColumnID) override; + +private: + TSharedPtr< FMetaDataLine > MetaDataLine; +}; + +void SMetaDataViewRow::Construct(const FArguments& InArgs, TSharedRef< FMetaDataLine > InMetadata, TSharedRef< STableViewBase > InOwnerTableView) +{ + MetaDataLine = InMetadata; + + SMultiColumnTableRow< TSharedPtr< FMetaDataLine > >::Construct(FSuperRowType::FArguments(), InOwnerTableView); +} + +TSharedRef< SWidget > SMetaDataViewRow::GenerateWidgetForColumn(const FName& ColumnID) +{ + TSharedPtr< SWidget > TableRowContent; + + static const FTextBlockStyle MetadataTextStyle = FTextBlockStyle(FCoreStyle::Get().GetWidgetStyle("NormalText")) + .SetFontSize(10); + + if (ColumnID == MetaDataViewColumns::ColumnID_Tag) + { + SAssignNew(TableRowContent, SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.5f) + .FillWidth(100.0f) + [ + SNew(SMultiLineEditableText) + .Text(FText::FromName(MetaDataLine->Tag)) + .TextStyle(&MetadataTextStyle) + .IsReadOnly(true) + ]; + } + else if (ColumnID == MetaDataViewColumns::ColumnID_Value) + { + SAssignNew(TableRowContent, SHorizontalBox) + + SHorizontalBox::Slot() + .Padding(1.5f) + .FillWidth(400.0f) + [ + SNew(SMultiLineEditableText) + .Text(FText::FromString(*(MetaDataLine->Value))) + .TextStyle(&MetadataTextStyle) + .IsReadOnly(true) + .AutoWrapText(true) + ]; + } + else + { + checkf(false, TEXT("Unknown ColumnID provided to SMetaDataView")); + } + + return TableRowContent.ToSharedRef(); +} + +void SMetaDataView::Construct(const FArguments& InArgs, const TMap& InMetadata) +{ + for (auto It = InMetadata.CreateConstIterator(); It; ++It) + { + MetaDataLines.Add(MakeShared(FMetaDataLine(It->Key, It->Value))); + } + + TSharedPtr< SHeaderRow > HeaderRowWidget = + SNew(SHeaderRow) + + // Tag column + + SHeaderRow::Column(MetaDataViewColumns::ColumnID_Tag) + .FillWidth(100.0f) + .DefaultLabel(NSLOCTEXT("MetadataView", "ColumnID_Tag", "Tag")) + .DefaultTooltip(FText()) + + // Value column + + SHeaderRow::Column(MetaDataViewColumns::ColumnID_Value) + .FillWidth(400.0f) + .DefaultLabel(NSLOCTEXT("MetadataView", "ColumnID_Value", "Value")) + .DefaultTooltip(FText()); + + ChildSlot + [ + SNew(SVerticalBox) + + SVerticalBox::Slot() + [ + SNew(SListView< TSharedPtr< FMetaDataLine > >) + .ListItemsSource(&MetaDataLines) + .OnGenerateRow(this, &SMetaDataView::OnGenerateRow) + .HeaderRow(HeaderRowWidget) + ] + ]; +} + +TSharedRef< ITableRow > SMetaDataView::OnGenerateRow(const TSharedPtr< FMetaDataLine > Item, const TSharedRef< STableViewBase >& OwnerTable) +{ + return SNew(SMetaDataViewRow, Item.ToSharedRef(), OwnerTable); +} diff --git a/Engine/Source/Editor/ContentBrowser/Private/SMetaDataView.h b/Engine/Source/Editor/ContentBrowser/Private/SMetaDataView.h new file mode 100644 index 000000000000..8058da1231bd --- /dev/null +++ b/Engine/Source/Editor/ContentBrowser/Private/SMetaDataView.h @@ -0,0 +1,34 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" + +class ITableRow; +class STableViewBase; + +struct FMetaDataLine; + +/** + * The widget to display metadata as a table of tag/value rows + */ +class SMetaDataView : public SCompoundWidget +{ +public: + SLATE_BEGIN_ARGS(SMetaDataView) {} + SLATE_END_ARGS() + + /** + * Construct this widget. Called by the SNew() Slate macro. + * + * @param InArgs Declaration used by the SNew() macro to construct this widget + * @param InMetaData The metadata tags/values to display in the table view widget + */ + void Construct(const FArguments& InArgs, const TMap& InMetadata); + +private: + TArray< TSharedPtr< FMetaDataLine > > MetaDataLines; + + TSharedRef< ITableRow > OnGenerateRow(const TSharedPtr< FMetaDataLine > Item, const TSharedRef< STableViewBase >& OwnerTable); +}; diff --git a/Engine/Source/Editor/DetailCustomizations/Private/FrameRateCustomization.cpp b/Engine/Source/Editor/DetailCustomizations/Private/FrameRateCustomization.cpp index 455295d431f9..d0e5f6edbc71 100644 --- a/Engine/Source/Editor/DetailCustomizations/Private/FrameRateCustomization.cpp +++ b/Engine/Source/Editor/DetailCustomizations/Private/FrameRateCustomization.cpp @@ -3,6 +3,8 @@ #include "FrameRateCustomization.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SFrameRatePicker.h" +#include "IPropertyUtilities.h" +#include "IPropertyTypeCustomization.h" #include "PropertyHandle.h" #include "DetailLayoutBuilder.h" #include "DetailWidgetRow.h" @@ -28,6 +30,8 @@ void FFrameRateCustomization::CustomizeHeader(TSharedRef InProp void FFrameRateCustomization::CustomizeChildren(TSharedRef InPropertyHandle, IDetailChildrenBuilder& ChildBuilder, IPropertyTypeCustomizationUtils& CustomizationUtils) { + TSharedPtr PropertyUtils = CustomizationUtils.GetPropertyUtilities(); + FDetailWidgetRow& CustomRow = ChildBuilder.AddCustomRow(StructPropertyHandle->GetPropertyDisplayName()); CustomRow.NameContent() @@ -42,7 +46,7 @@ void FFrameRateCustomization::CustomizeChildren(TSharedRef InPr .HasMultipleValues(this, &FFrameRateCustomization::HasMultipleValues) .Value(this, &FFrameRateCustomization::GetFirstFrameRate) .OnValueChanged(this, &FFrameRateCustomization::SetFrameRate) - ]; + ].IsEnabled(MakeAttributeLambda([=] { return !InPropertyHandle->IsEditConst() && PropertyUtils->IsPropertyEditingEnabled(); })); } @@ -61,22 +65,16 @@ FFrameRate FFrameRateCustomization::GetFirstFrameRate() const void FFrameRateCustomization::SetFrameRate(FFrameRate NewFrameRate) { - GEditor->BeginTransaction(FText::Format(LOCTEXT("EditProperty", "Edit {0}"), StructPropertyHandle->GetPropertyDisplayName())); - - StructPropertyHandle->NotifyPreChange(); - - TArray RawData; - StructPropertyHandle->AccessRawData(RawData); - - for (void* RawPtr : RawData) + if (UStructProperty* StructProperty = Cast(StructPropertyHandle->GetProperty())) { - *reinterpret_cast(RawPtr) = NewFrameRate; + TArray RawData; + StructPropertyHandle->AccessRawData(RawData); + FFrameRate* PreviousFrameRate = reinterpret_cast(RawData[0]); + + FString TextValue; + StructProperty->Struct->ExportText(TextValue, &NewFrameRate, PreviousFrameRate, nullptr, EPropertyPortFlags::PPF_None, nullptr); + ensure(StructPropertyHandle->SetValueFromFormattedString(TextValue, EPropertyValueSetFlags::InteractiveChange) == FPropertyAccess::Result::Success); } - - StructPropertyHandle->NotifyPostChange(); - StructPropertyHandle->NotifyFinishedChangingProperties(); - - GEditor->EndTransaction(); } bool FFrameRateCustomization::HasMultipleValues() const diff --git a/Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.cpp b/Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.cpp new file mode 100644 index 000000000000..6d814b0df272 --- /dev/null +++ b/Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.cpp @@ -0,0 +1,243 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +#include "PluginWardenAuthorizer.h" +#include "PluginWardenModule.h" + +#include "Containers/Ticker.h" +#include "Async/TaskGraphInterfaces.h" +#include "Editor.h" + +#include "Logging/LogMacros.h" + +#include "IPortalServiceLocator.h" +#include "Account/IPortalUserLogin.h" +#include "Application/IPortalApplicationWindow.h" + +#include "ILauncherPlatform.h" +#include "LauncherPlatformModule.h" + +extern TSet AuthorizedPlugins; + +DEFINE_LOG_CATEGORY(PluginWarden); + +#define LOCTEXT_NAMESPACE "PluginWardenAuthorization" + +FPluginWardenAuthorizer::FPluginWardenAuthorizer(const FText& InPluginFriendlyName, const FString& InPluginItemId, const FString& InPluginOfferId) + : CurrentState(EPluginAuthorizationState::Initializing) + , WaitingTime(0) + , PluginFriendlyName(InPluginFriendlyName) + , PluginItemId(InPluginItemId) + , PluginOfferId(InPluginOfferId) +{ + TSharedRef ServiceLocator = GEditor->GetServiceLocator(); + PortalWindowService = ServiceLocator->GetServiceRef(); + PortalUserService = ServiceLocator->GetServiceRef(); + PortalUserLoginService = ServiceLocator->GetServiceRef(); +} + +EPluginAuthorizationState FPluginWardenAuthorizer::UpdateAuthorizationState(float DeltaTime) +{ + EPluginAuthorizationState NewState = CurrentState; + + switch ( CurrentState ) + { + case EPluginAuthorizationState::Initializing: + { + WaitingTime = 0; + if ( PortalWindowService->IsAvailable() && PortalUserService->IsAvailable() ) + { + NewState = EPluginAuthorizationState::AuthorizePlugin; + } + else + { + NewState = EPluginAuthorizationState::StartLauncher; + } + break; + } + case EPluginAuthorizationState::StartLauncher: + { + WaitingTime = 0; + ILauncherPlatform* LauncherPlatform = FLauncherPlatformModule::Get(); + + if (LauncherPlatform != nullptr ) + { + if ( !FPlatformProcess::IsApplicationRunning(TEXT("EpicGamesLauncher")) && + !FPlatformProcess::IsApplicationRunning(TEXT("EpicGamesLauncher-Mac-Shipping")) ) + { + FOpenLauncherOptions SilentOpen; + if (LauncherPlatform->OpenLauncher(SilentOpen) ) + { + NewState = EPluginAuthorizationState::StartLauncher_Waiting; + } + else + { + NewState = EPluginAuthorizationState::LauncherStartFailed; + } + } + else + { + // If the process is found to be running already, move into the next state. + NewState = EPluginAuthorizationState::StartLauncher_Waiting; + } + } + else + { + NewState = EPluginAuthorizationState::LauncherStartFailed; + } + break; + } + case EPluginAuthorizationState::StartLauncher_Waiting: + { + UE_LOG(PluginWarden, Log, TEXT("Waiting for launcher to run for the past %f seconds"), WaitingTime); + if ( FPlatformProcess::IsApplicationRunning(TEXT("EpicGamesLauncher")) && PortalWindowService->IsAvailable() && PortalUserService->IsAvailable() ) + { + NewState = EPluginAuthorizationState::AuthorizePlugin; + } + else + { + WaitingTime += DeltaTime; + } + break; + } + case EPluginAuthorizationState::AuthorizePlugin: + { + WaitingTime = 0; + EntitlementResult = PortalUserService->IsEntitledToItem(PluginItemId, EEntitlementCacheLevelRequest::Memory); + NewState = EPluginAuthorizationState::AuthorizePlugin_Waiting; + break; + } + case EPluginAuthorizationState::AuthorizePlugin_Waiting: + { + WaitingTime += DeltaTime; + + check(EntitlementResult.GetFuture().IsValid()); + if ( EntitlementResult.GetFuture().IsReady() ) + { + FPortalUserIsEntitledToItemResult Entitlement = EntitlementResult.GetFuture().Get(); + if ( Entitlement.IsEntitled ) + { + NewState = EPluginAuthorizationState::Authorized; + } + else + { + NewState = EPluginAuthorizationState::IsUserSignedIn; + } + } + + break; + } + case EPluginAuthorizationState::IsUserSignedIn: + { + WaitingTime = 0; + UserDetailsResult = PortalUserService->GetUserDetails(); + NewState = EPluginAuthorizationState::IsUserSignedIn_Waiting; + break; + } + case EPluginAuthorizationState::IsUserSignedIn_Waiting: + { + WaitingTime += DeltaTime; + + check(UserDetailsResult.GetFuture().IsValid()); + if ( UserDetailsResult.GetFuture().IsReady() ) + { + FPortalUserDetails UserDetails = UserDetailsResult.GetFuture().Get(); + + if ( UserDetails.IsSignedIn ) + { + // if the user is signed in, and we're at this stage, we know they are unauthorized. + NewState = EPluginAuthorizationState::Unauthorized; + } + else + { + // If they're not signed in, but they were unauthorized, they may have purchased it + // they may just need to sign-in. + if ( PortalUserLoginService->IsAvailable() ) + { + NewState = EPluginAuthorizationState::SigninRequired; + } + } + } + + break; + } + case EPluginAuthorizationState::SigninRequired: + { + WaitingTime = 0; + UserSigninResult = PortalUserLoginService->PromptUserForSignIn(); + NewState = EPluginAuthorizationState::SigninRequired_Waiting; + break; + } + case EPluginAuthorizationState::SigninRequired_Waiting: + { + // We don't advance the wait time in the sign-required state, as this may take a long time. + + check(UserSigninResult.GetFuture().IsValid()); + if ( UserSigninResult.GetFuture().IsReady() ) + { + bool IsUserSignedIn = UserSigninResult.GetFuture().Get(); + if ( IsUserSignedIn ) + { + UserDetailsResult = PortalUserService->GetUserDetails(); + NewState = EPluginAuthorizationState::Signin_Waiting; + WaitingTime = 0; + } + else + { + NewState = EPluginAuthorizationState::Unauthorized; + } + } + + break; + } + // We stay in the Signin_Waiting state until the user is signed in or until they cancel the + // authorizing plug-in UI. It would be nice to be able to know if the user closes the sign-in + // dialog and cancel out of this dialog automatically. + case EPluginAuthorizationState::Signin_Waiting: + { + WaitingTime += DeltaTime; + UE_LOG(PluginWarden, Log, TEXT("Waiting for sign in for the past %f seconds"), WaitingTime); + + check(UserDetailsResult.GetFuture().IsValid()); + if ( UserDetailsResult.GetFuture().IsReady() ) + { + FPortalUserDetails UserDetails = UserDetailsResult.GetFuture().Get(); + + if ( UserDetails.IsSignedIn ) + { + // if the user is signed in, and we're at this stage, we know they are unauthorized. + NewState = EPluginAuthorizationState::AuthorizePlugin; + } + else + { + NewState = EPluginAuthorizationState::SigninFailed; + } + } + + break; + } + } + + // If we're in a waiting state, check to see if we're over the timeout period. + switch ( NewState ) + { + case EPluginAuthorizationState::StartLauncher_Waiting: + case EPluginAuthorizationState::AuthorizePlugin_Waiting: + case EPluginAuthorizationState::IsUserSignedIn_Waiting: + case EPluginAuthorizationState::SigninRequired_Waiting: + // We Ignore EPluginAuthorizationState::Signin_Waiting, that state could take forever, the user needs to sign-in or close the dialog. + { + const float TimeoutSeconds = 15; + if ( WaitingTime > TimeoutSeconds ) + { + NewState = EPluginAuthorizationState::Timeout; + } + break; + } + } + + CurrentState = NewState; + + return CurrentState; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.h b/Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.h new file mode 100644 index 000000000000..6930c576c877 --- /dev/null +++ b/Engine/Source/Editor/PluginWarden/Private/PluginWardenAuthorizer.h @@ -0,0 +1,79 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "Misc/CoreMisc.h" +#include "Containers/Ticker.h" +#include "Async/AsyncResult.h" +#include "Account/IPortalUser.h" + +class IPortalApplicationWindow; +class IPortalUserLogin; + +enum class EPluginAuthorizationState +{ + Initializing, + StartLauncher, + StartLauncher_Waiting, + AuthorizePlugin, + AuthorizePlugin_Waiting, + IsUserSignedIn, + IsUserSignedIn_Waiting, + SigninRequired, + SigninRequired_Waiting, + Signin_Waiting, + Signin_Timeout, + SigninFailed, + Authorized, + Unauthorized, + LauncherStartFailed, + Timeout, + Canceled, +}; + +class FPluginWardenAuthorizer +{ +public: + FPluginWardenAuthorizer(const FText& InPluginFriendlyName, const FString& InPluginItemId, const FString& InPluginOfferId); + + /** Returns new state after elapsed time */ + EPluginAuthorizationState UpdateAuthorizationState(float DeltaTime); + + const FText& GetPluginFriendlyName() { return PluginFriendlyName; } + const FString& GetPluginItemId() { return PluginItemId; } + const FString& GetPluginOfferId() { return PluginOfferId; } + +private: + /** The current state of the plug-in authorization pipeline. */ + EPluginAuthorizationState CurrentState; + + /** The amount of time we've been waiting for confirmation for a given step. It's possible a problem may arise and we need to timeout. */ + float WaitingTime; + + /** The display name of the plug-in used in the auto generated dialog text. */ + FText PluginFriendlyName; + + /** The unique id of the item for the plug-in on the marketplace. */ + FString PluginItemId; + + /** The unique id of the offer for the plug-in on the marketplace. */ + FString PluginOfferId; + + /** The portal application communication service. */ + TSharedPtr PortalWindowService; + + /** The portal user service, to allow us to check entitlements for plugins. */ + TSharedPtr PortalUserService; + + /** The portal user login service, to allow us to trigger a prompt to sign-in, if required. */ + TSharedPtr PortalUserLoginService; + + /** The entitlement result we may be waiting on. */ + TAsyncResult EntitlementResult; + + /** The result from the request for user details. */ + TAsyncResult UserDetailsResult; + + /** The result from requesting the user sign-in, they may sign-in, they may cancel. */ + TAsyncResult UserSigninResult; +}; diff --git a/Engine/Source/Editor/PluginWarden/Private/PluginWardenModule.cpp b/Engine/Source/Editor/PluginWarden/Private/PluginWardenModule.cpp index 067abd9c8150..f0351665aa9a 100644 --- a/Engine/Source/Editor/PluginWarden/Private/PluginWardenModule.cpp +++ b/Engine/Source/Editor/PluginWarden/Private/PluginWardenModule.cpp @@ -1,10 +1,12 @@ // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. #include "PluginWardenModule.h" +#include "Async/TaskGraphInterfaces.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/SWindow.h" #include "Framework/Application/SlateApplication.h" +#include "PluginWardenAuthorizer.h" #include "SAuthorizingPlugin.h" IMPLEMENT_MODULE( FPluginWardenModule, PluginWarden ); @@ -15,7 +17,7 @@ TSet AuthorizedPlugins; void FPluginWardenModule::StartupModule() { - + } void FPluginWardenModule::ShutdownModule() @@ -32,20 +34,137 @@ void FPluginWardenModule::CheckEntitlementForPlugin(const FText& PluginFriendlyN return; } - // Create the window - TSharedRef AuthorizingPluginWindow = SNew(SWindow) - .SupportsMaximize(false) - .SupportsMinimize(false) - .HasCloseButton(true) - .SizingRule(ESizingRule::Autosized) - .Title(FText::Format(LOCTEXT("EntitlementCheckFormat", "{0} - Entitlement Check"), PluginFriendlyName)); + if (IsRunningCommandlet() || GIsRunningUnattendedScript) + { + if (RunAuthorizationPipeline(PluginFriendlyName, PluginItemId, PluginOfferId)) + { + AuthorizedPlugins.Add(PluginItemId); + AuthorizedCallback(); + } + } + else + { + // Create the window + TSharedRef AuthorizingPluginWindow = SNew(SWindow) + .SupportsMaximize(false) + .SupportsMinimize(false) + .HasCloseButton(true) + .SizingRule(ESizingRule::Autosized) + .Title(FText::Format(LOCTEXT("EntitlementCheckFormat", "{0} - Entitlement Check"), PluginFriendlyName)); - TSharedRef PluginAuthPanel = SNew(SAuthorizingPlugin, AuthorizingPluginWindow, PluginFriendlyName, PluginItemId, PluginOfferId, AuthorizedCallback); - PluginAuthPanel->SetUnauthorizedOverride(UnauthorizedMessageOverride, UnauthorizedErrorHandling); + TSharedRef PluginAuthPanel = SNew(SAuthorizingPlugin, AuthorizingPluginWindow, PluginFriendlyName, PluginItemId, PluginOfferId, AuthorizedCallback); + PluginAuthPanel->SetUnauthorizedOverride(UnauthorizedMessageOverride, UnauthorizedErrorHandling); - AuthorizingPluginWindow->SetContent(PluginAuthPanel); + AuthorizingPluginWindow->SetContent(PluginAuthPanel); - FSlateApplication::Get().AddModalWindow(AuthorizingPluginWindow, nullptr); + FSlateApplication::Get().AddModalWindow(AuthorizingPluginWindow, nullptr); + } +} + +bool FPluginWardenModule::RunAuthorizationPipeline(const FText& PluginFriendlyName, const FString& PluginItemId, const FString& PluginOfferId) +{ + FPluginWardenAuthorizer Authorizer(PluginFriendlyName, PluginItemId, PluginOfferId); + + EPluginAuthorizationState AuthorizationState = EPluginAuthorizationState::Initializing; + bool bAuthorizationCompleted = false; + + double LastLoopTime = FPlatformTime::Seconds(); + double LastTickTime = FPlatformTime::Seconds(); + const float MinThrottlePeriod = (1.0f / 60.0f); //Throttle the loop to a maximum of 60Hz + + while (!bAuthorizationCompleted) + { + //Throttle the loop + const double CurrentLoopTime = FPlatformTime::Seconds(); + const float SleepTime = MinThrottlePeriod - (float)(CurrentLoopTime-LastLoopTime); + LastLoopTime = CurrentLoopTime; + if (SleepTime > 0.0f) + { + // Sleep a bit to not eat up all CPU time + FPlatformProcess::Sleep(SleepTime); + } + + const double CurrentTickTime = FPlatformTime::Seconds(); + float DeltaTime = (float)(CurrentTickTime - LastTickTime); + LastTickTime = CurrentTickTime; + + FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); + FTicker::GetCoreTicker().Tick(DeltaTime); + + const EPluginAuthorizationState PreviousState = AuthorizationState; + AuthorizationState = Authorizer.UpdateAuthorizationState(DeltaTime); + + switch ( AuthorizationState ) + { + case EPluginAuthorizationState::Canceled: + case EPluginAuthorizationState::Authorized: + case EPluginAuthorizationState::Unauthorized: + case EPluginAuthorizationState::Timeout: + case EPluginAuthorizationState::LauncherStartFailed: + case EPluginAuthorizationState::SigninFailed: + { + bAuthorizationCompleted = true; + break; + } + } + + if (PreviousState != AuthorizationState) + { + switch ( AuthorizationState ) + { + case EPluginAuthorizationState::StartLauncher_Waiting: + { + UE_LOG(PluginWarden, Log, TEXT("Waiting for launcher ...")); + break; + } + case EPluginAuthorizationState::SigninRequired_Waiting: + { + UE_LOG(PluginWarden, Log, TEXT("Sign-in required ...")); + break; + } + case EPluginAuthorizationState::Signin_Waiting: + { + UE_LOG(PluginWarden, Log, TEXT("Signing in ...")); + break; + } + case EPluginAuthorizationState::AuthorizePlugin_Waiting: + { + UE_LOG(PluginWarden, Log, TEXT("Waiting for authorization on plug-in ...")); + break; + } + } + } + } + + switch (AuthorizationState) + { + case EPluginAuthorizationState::Authorized: + { + return true; + } + case EPluginAuthorizationState::Unauthorized: + { + UE_LOG(PluginWarden, Warning, TEXT("It looks like your Epic Games account doesn't have entitlements for %s."), *PluginFriendlyName.ToString()); + break; + } + case EPluginAuthorizationState::Timeout: + { + UE_LOG(PluginWarden, Error, TEXT("We were unable to verify your access to the plugin before timing out.")); + break; + } + case EPluginAuthorizationState::LauncherStartFailed: + { + UE_LOG(PluginWarden, Error, TEXT("Cannot start the launcher. Please open the launcher and sign in manually.")); + break; + } + case EPluginAuthorizationState::SigninFailed: + { + UE_LOG(PluginWarden, Error, TEXT("Sign-in failed. Please sign in manually through the launcher.")); + break; + } + } + + return false; } #undef LOCTEXT_NAMESPACE diff --git a/Engine/Source/Editor/PluginWarden/Private/PluginWardenModule.h b/Engine/Source/Editor/PluginWarden/Private/PluginWardenModule.h index ca9b7af06c6b..c3a597624d9c 100644 --- a/Engine/Source/Editor/PluginWarden/Private/PluginWardenModule.h +++ b/Engine/Source/Editor/PluginWarden/Private/PluginWardenModule.h @@ -4,6 +4,8 @@ #include "CoreMinimal.h" #include "IPluginWardenModule.h" +DECLARE_LOG_CATEGORY_EXTERN(PluginWarden, Log, All); + /** * The Plugin Warden is a simple module used to verify a user has purchased a plug-in. This * module won't prevent a determined user from avoiding paying for a plug-in, it is merely to @@ -25,14 +27,14 @@ public: virtual void ShutdownModule(); /** - * Ask the launcher if the user has authorization to use the given plug-in. The authorized + * Ask the launcher if the user has authorization to use the given plug-in. The authorized * callback will only be called if the user is authorized to use the plug-in. - * + * * FPluginWardenModule& PluginWarden = FModuleManager::LoadModuleChecked("PluginWarden"); * PluginWarden.CheckEntitlementForPlugin(LOCTEXT("AwesomePluginName", "My Awesome Plugin"), TEXT("PLUGIN_MARKETPLACE_GUID"), [&] () { * // Authorized Code Here * }); - * + * * @param PluginFriendlyName The localized friendly name of the plug-in. * @param PluginItemId The unique identifier of the item plug-in on the marketplace. * @param PluginOfferId The unique identifier of the offer for the plug-in on the marketplace. @@ -41,4 +43,7 @@ public: * @param AuthorizedCallback This function will be called after the user has been given entitlement. */ virtual void CheckEntitlementForPlugin(const FText& PluginFriendlyName, const FString& PluginItemId, const FString& PluginOfferId, const FText& UnauthorizedMessageOverride, EUnauthorizedErrorHandling UnauthorizedErrorHandling, TFunction AuthorizedCallback) override; + +private: + bool RunAuthorizationPipeline(const FText& PluginFriendlyName, const FString& PluginItemId, const FString& PluginOfferId); }; diff --git a/Engine/Source/Editor/PluginWarden/Private/SAuthorizingPlugin.cpp b/Engine/Source/Editor/PluginWarden/Private/SAuthorizingPlugin.cpp index 92747166a699..a2d99f4ec380 100644 --- a/Engine/Source/Editor/PluginWarden/Private/SAuthorizingPlugin.cpp +++ b/Engine/Source/Editor/PluginWarden/Private/SAuthorizingPlugin.cpp @@ -1,6 +1,7 @@ // Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. #include "SAuthorizingPlugin.h" +#include "PluginWardenAuthorizer.h" #include "EngineAnalytics.h" #include "Misc/MessageDialog.h" #include "Containers/Ticker.h" @@ -15,10 +16,6 @@ #include "Editor.h" #include "Widgets/Images/SThrobber.h" -#include "IPortalServiceLocator.h" -#include "Account/IPortalUserLogin.h" -#include "Application/IPortalApplicationWindow.h" - #include "ILauncherPlatform.h" #include "LauncherPlatformModule.h" @@ -26,15 +23,12 @@ void SAuthorizingPlugin::Construct(const FArguments& InArgs, const TSharedRef& InParentWindow, const FText& InPluginFriendlyName, const FString& InPluginItemId, const FString& InPluginOfferId, TFunction InAuthorizedCallback) { - CurrentState = EPluginAuthorizationState::Initializing; - WaitingTime = 0; ParentWindow = InParentWindow; - PluginFriendlyName = InPluginFriendlyName; - PluginItemId = InPluginItemId; - PluginOfferId = InPluginOfferId; AuthorizedCallback = InAuthorizedCallback; UnauthorizedErrorHandling = IPluginWardenModule::EUnauthorizedErrorHandling::ShowMessageOpenStore; + Authorizer = MakeShared(InPluginFriendlyName, InPluginItemId, InPluginOfferId); + InParentWindow->SetOnWindowClosed(FOnWindowClosed::CreateSP(this, &SAuthorizingPlugin::OnWindowClosed)); bUserInterrupted = true; @@ -86,11 +80,6 @@ void SAuthorizingPlugin::Construct(const FArguments& InArgs, const TSharedRef ServiceLocator = GEditor->GetServiceLocator(); - PortalWindowService = ServiceLocator->GetServiceRef(); - PortalUserService = ServiceLocator->GetServiceRef(); - PortalUserLoginService = ServiceLocator->GetServiceRef(); } void SAuthorizingPlugin::SetUnauthorizedOverride(const FText & InUnauthorizedMessageOverride, IPluginWardenModule::EUnauthorizedErrorHandling InUnauthorizedErrorHandling) @@ -101,7 +90,7 @@ void SAuthorizingPlugin::SetUnauthorizedOverride(const FText & InUnauthorizedMes FText SAuthorizingPlugin::GetWaitingText() const { - switch ( CurrentState ) + switch ( AuthorizationState ) { case EPluginAuthorizationState::Initializing: case EPluginAuthorizationState::StartLauncher: @@ -110,15 +99,17 @@ FText SAuthorizingPlugin::GetWaitingText() const return LOCTEXT("ConnectingToLauncher", "Connecting..."); case EPluginAuthorizationState::AuthorizePlugin: case EPluginAuthorizationState::AuthorizePlugin_Waiting: - return FText::Format(LOCTEXT("CheckingIfYouCanUseFormat", "Checking license for {0}..."), PluginFriendlyName); + return FText::Format(LOCTEXT("CheckingIfYouCanUseFormat", "Checking license for {0}..."), Authorizer->GetPluginFriendlyName()); case EPluginAuthorizationState::IsUserSignedIn: case EPluginAuthorizationState::IsUserSignedIn_Waiting: return LOCTEXT("CheckingIfUserSignedIn", "Authorization failed, checking user information..."); case EPluginAuthorizationState::SigninRequired: case EPluginAuthorizationState::SigninRequired_Waiting: - return LOCTEXT("NeedUserToLoginToCheck", "Authorization failed, Sign-in required..."); + return LOCTEXT("NeedUserToLoginToCheck", "Authorization failed, sign-in required..."); case EPluginAuthorizationState::Signin_Waiting: - return LOCTEXT("WaitingForSignin", "Waiting for Sign-in..."); + return LOCTEXT("WaitingForSignin", "Waiting for sign-in..."); + case EPluginAuthorizationState::SigninFailed: + return LOCTEXT("SigninFailed", "Sign-in failed. Cancel and sign in manually through the launcher."); } return LOCTEXT("Processing", "Processing..."); @@ -131,179 +122,10 @@ EActiveTimerReturnType SAuthorizingPlugin::RefreshStatus(double InCurrentTime, f FTaskGraphInterface::Get().ProcessThreadUntilIdle(ENamedThreads::GameThread); FTicker::GetCoreTicker().Tick(InDeltaTime); - switch ( CurrentState ) + AuthorizationState = Authorizer->UpdateAuthorizationState(InDeltaTime); + + switch ( AuthorizationState ) { - case EPluginAuthorizationState::Initializing: - { - WaitingTime = 0; - if ( PortalWindowService->IsAvailable() && PortalUserService->IsAvailable() ) - { - CurrentState = EPluginAuthorizationState::AuthorizePlugin; - } - else - { - CurrentState = EPluginAuthorizationState::StartLauncher; - } - break; - } - case EPluginAuthorizationState::StartLauncher: - { - WaitingTime = 0; - ILauncherPlatform* LauncherPlatform = FLauncherPlatformModule::Get(); - - if (LauncherPlatform != nullptr ) - { - if ( !FPlatformProcess::IsApplicationRunning(TEXT("EpicGamesLauncher")) && - !FPlatformProcess::IsApplicationRunning(TEXT("EpicGamesLauncher-Mac-Shipping")) ) - { - FOpenLauncherOptions SilentOpen; - if (LauncherPlatform->OpenLauncher(SilentOpen) ) - { - CurrentState = EPluginAuthorizationState::StartLauncher_Waiting; - } - else - { - CurrentState = EPluginAuthorizationState::LauncherStartFailed; - } - } - else - { - // If the process is found to be running already, move into the next state. - CurrentState = EPluginAuthorizationState::StartLauncher_Waiting; - } - } - else - { - CurrentState = EPluginAuthorizationState::LauncherStartFailed; - } - break; - } - case EPluginAuthorizationState::StartLauncher_Waiting: - { - if ( PortalWindowService->IsAvailable() && PortalUserService->IsAvailable() ) - { - CurrentState = EPluginAuthorizationState::AuthorizePlugin; - } - else - { - WaitingTime += InDeltaTime; - } - break; - } - case EPluginAuthorizationState::AuthorizePlugin: - { - WaitingTime = 0; - EntitlementResult = PortalUserService->IsEntitledToItem(PluginItemId, EEntitlementCacheLevelRequest::Memory); - CurrentState = EPluginAuthorizationState::AuthorizePlugin_Waiting; - break; - } - case EPluginAuthorizationState::AuthorizePlugin_Waiting: - { - WaitingTime += InDeltaTime; - - check(EntitlementResult.GetFuture().IsValid()); - if ( EntitlementResult.GetFuture().IsReady() ) - { - FPortalUserIsEntitledToItemResult Entitlement = EntitlementResult.GetFuture().Get(); - if ( Entitlement.IsEntitled ) - { - CurrentState = EPluginAuthorizationState::Authorized; - } - else - { - CurrentState = EPluginAuthorizationState::IsUserSignedIn; - } - } - - break; - } - case EPluginAuthorizationState::IsUserSignedIn: - { - WaitingTime = 0; - UserDetailsResult = PortalUserService->GetUserDetails(); - CurrentState = EPluginAuthorizationState::IsUserSignedIn_Waiting; - break; - } - case EPluginAuthorizationState::IsUserSignedIn_Waiting: - { - WaitingTime += InDeltaTime; - - check(UserDetailsResult.GetFuture().IsValid()); - if ( UserDetailsResult.GetFuture().IsReady() ) - { - FPortalUserDetails UserDetails = UserDetailsResult.GetFuture().Get(); - - if ( UserDetails.IsSignedIn ) - { - // if the user is signed in, and we're at this stage, we know they are unauthorized. - CurrentState = EPluginAuthorizationState::Unauthorized; - } - else - { - // If they're not signed in, but they were unauthorized, they may have purchased it - // they may just need to sign-in. - if ( PortalUserLoginService->IsAvailable() ) - { - CurrentState = EPluginAuthorizationState::SigninRequired; - } - } - } - - break; - } - case EPluginAuthorizationState::SigninRequired: - { - WaitingTime = 0; - UserSigninResult = PortalUserLoginService->PromptUserForSignIn(); - CurrentState = EPluginAuthorizationState::SigninRequired_Waiting; - break; - } - case EPluginAuthorizationState::SigninRequired_Waiting: - { - // We don't advance the wait time in the sign-required state, as this may take a long time. - - check(UserSigninResult.GetFuture().IsValid()); - if ( UserSigninResult.GetFuture().IsReady() ) - { - bool IsUserSignedIn = UserSigninResult.GetFuture().Get(); - if ( IsUserSignedIn ) - { - UserDetailsResult = PortalUserService->GetUserDetails(); - CurrentState = EPluginAuthorizationState::Signin_Waiting; - } - else - { - CurrentState = EPluginAuthorizationState::Unauthorized; - } - } - - break; - } - // We stay in the Signin_Waiting state until the user is signed in or until they cancel the - // authorizing plug-in UI. It would be nice to be able to know if the user closes the sign-in - // dialog and cancel out of this dialog automatically. - case EPluginAuthorizationState::Signin_Waiting: - { - WaitingTime = 0; - - check(UserDetailsResult.GetFuture().IsValid()); - if ( UserDetailsResult.GetFuture().IsReady() ) - { - FPortalUserDetails UserDetails = UserDetailsResult.GetFuture().Get(); - - if ( UserDetails.IsSignedIn ) - { - // if the user is signed in, and we're at this stage, we know they are unauthorized. - CurrentState = EPluginAuthorizationState::AuthorizePlugin; - } - else - { - UserDetailsResult = PortalUserService->GetUserDetails(); - } - } - - break; - } case EPluginAuthorizationState::Authorized: case EPluginAuthorizationState::Unauthorized: case EPluginAuthorizationState::Timeout: @@ -319,31 +141,6 @@ EActiveTimerReturnType SAuthorizingPlugin::RefreshStatus(double InCurrentTime, f ParentWindow.Pin()->RequestDestroyWindow(); break; } - default: - { - // Should never be in a non-explicit state. - check(false); - break; - } - } - - // If we're in a waiting state, check to see if we're over the timeout period. - switch ( CurrentState ) - { - case EPluginAuthorizationState::StartLauncher_Waiting: - case EPluginAuthorizationState::AuthorizePlugin_Waiting: - case EPluginAuthorizationState::IsUserSignedIn_Waiting: - case EPluginAuthorizationState::SigninRequired_Waiting: - // We Ignore EPluginAuthorizationState::Signin_Waiting, that state could take forever, the user needs to sign-in or close the dialog. - { - const float TimeoutSeconds = 15; - if ( WaitingTime > TimeoutSeconds ) - { - bUserInterrupted = false; - CurrentState = EPluginAuthorizationState::Timeout; - } - break; - } } return EActiveTimerReturnType::Continue; @@ -359,23 +156,20 @@ FReply SAuthorizingPlugin::OnCancel() void SAuthorizingPlugin::OnWindowClosed(const TSharedRef& InWindow) { - if ( bUserInterrupted || CurrentState == EPluginAuthorizationState::Canceled ) + if ( bUserInterrupted || AuthorizationState == EPluginAuthorizationState::Canceled ) { // User interrupted or canceled, just close down. } else { - if ( CurrentState == EPluginAuthorizationState::Authorized ) + switch ( AuthorizationState ) { - AuthorizedPlugins.Add(PluginItemId); - AuthorizedCallback(); - } - else - { - FEngineAnalytics::GetProvider().RecordEvent( TEXT("PluginWarden.AuthorizationFailure"), FAnalyticsEventAttribute( TEXT("State"), (int32)CurrentState ) ); - - switch ( CurrentState ) + case EPluginAuthorizationState::Authorized: { + AuthorizedPlugins.Add(Authorizer->GetPluginItemId()); + AuthorizedCallback(); + return; + } case EPluginAuthorizationState::Timeout: { FMessageDialog::Open(EAppMsgType::Ok, LOCTEXT("TimeoutFailure", "Something went wrong. We were unable to verify your access to the plugin before timing out.")); @@ -391,40 +185,41 @@ void SAuthorizingPlugin::OnWindowClosed(const TSharedRef& InWindow) FText FailureMessage = UnauthorizedMessageOverride; if (FailureMessage.IsEmpty()) { - FailureMessage = FText::Format(LOCTEXT("UnathorizedFailure", "It doesn't look like you've purchased {0}.\n\nWould you like to see the store page?"), PluginFriendlyName); + FailureMessage = FText::Format(LOCTEXT("UnauthorizedFailure", "It doesn't look like you've purchased {0}.\n\nWould you like to see the store page?"), Authorizer->GetPluginFriendlyName()); } switch (UnauthorizedErrorHandling) { - case IPluginWardenModule::EUnauthorizedErrorHandling::ShowMessageOpenStore: - { - EAppReturnType::Type Response = FMessageDialog::Open(EAppMsgType::YesNo, FailureMessage); - if (Response == EAppReturnType::Yes) + case IPluginWardenModule::EUnauthorizedErrorHandling::ShowMessageOpenStore: { - ShowStorePageForPlugin(); + EAppReturnType::Type Response = FMessageDialog::Open(EAppMsgType::YesNo, FailureMessage); + if (Response == EAppReturnType::Yes) + { + ShowStorePageForPlugin(); + } + break; } - break; - } - case IPluginWardenModule::EUnauthorizedErrorHandling::ShowMessage: - { - FMessageDialog::Open(EAppMsgType::Ok, FailureMessage); - break; - } - case IPluginWardenModule::EUnauthorizedErrorHandling::Silent: - default: - // Do nothing - break; + case IPluginWardenModule::EUnauthorizedErrorHandling::ShowMessage: + { + FMessageDialog::Open(EAppMsgType::Ok, FailureMessage); + break; + } + case IPluginWardenModule::EUnauthorizedErrorHandling::Silent: + default: + // Do nothing + break; } break; } default: { - // We expect all failure situations to have an explicit case. + // We expect all exit situations to have an explicit case. check(false); break; } - } } + + FEngineAnalytics::GetProvider().RecordEvent( TEXT("PluginWarden.AuthorizationFailure"), FAnalyticsEventAttribute( TEXT("State"), (int32)AuthorizationState ) ); } } @@ -434,7 +229,7 @@ void SAuthorizingPlugin::ShowStorePageForPlugin() if (LauncherPlatform != nullptr ) { - FOpenLauncherOptions StorePageOpen(FString(TEXT("/ue/marketplace/content/")) + PluginOfferId); + FOpenLauncherOptions StorePageOpen(FString(TEXT("/ue/marketplace/content/")) + Authorizer->GetPluginOfferId()); LauncherPlatform->OpenLauncher(StorePageOpen); } } diff --git a/Engine/Source/Editor/PluginWarden/Private/SAuthorizingPlugin.h b/Engine/Source/Editor/PluginWarden/Private/SAuthorizingPlugin.h index 861a106e0834..e772beb7104e 100644 --- a/Engine/Source/Editor/PluginWarden/Private/SAuthorizingPlugin.h +++ b/Engine/Source/Editor/PluginWarden/Private/SAuthorizingPlugin.h @@ -7,33 +7,13 @@ #include "Widgets/SCompoundWidget.h" #include "Widgets/SWindow.h" #include "Async/AsyncResult.h" -#include "Account/IPortalUser.h" #include "IPluginWardenModule.h" +#include "PluginWardenAuthorizer.h" -class IPortalApplicationWindow; -class IPortalUserLogin; +class FPluginWardenAuthorizer; extern TSet AuthorizedPlugins; -enum class EPluginAuthorizationState -{ - Initializing, - StartLauncher, - StartLauncher_Waiting, - AuthorizePlugin, - AuthorizePlugin_Waiting, - IsUserSignedIn, - IsUserSignedIn_Waiting, - SigninRequired, - SigninRequired_Waiting, - Signin_Waiting, - Authorized, - Unauthorized, - LauncherStartFailed, - Timeout, - Canceled, -}; - /** * The authorizing plug-in ui guides the user through the process of certifying their access to the plug-in. */ @@ -72,15 +52,6 @@ private: /** The parent window holding this dialog, for when we need to trigger a close. */ TWeakPtr ParentWindow; - /** The display name of the plug-in used in the auto generated dialog text. */ - FText PluginFriendlyName; - - /** The unique id of the item for the plug-in on the marketplace. */ - FString PluginItemId; - - /** The unique id of the offer for the plug-in on the marketplace. */ - FString PluginOfferId; - /** The optional error message to display in case plugin is unauthorized. If empty, will default to standard message. */ FText UnauthorizedMessageOverride; @@ -90,30 +61,12 @@ private: /** Flag for tracking user interruption of the process, either with the cancel button or the close button. */ bool bUserInterrupted; - /** The amount of time we've been waiting for confirmation for a given step. It's possible a problem may arise and we need to timeout. */ - float WaitingTime; - - /** The portal application communication service. */ - TSharedPtr PortalWindowService; - - /** The portal user service, to allow us to check entitlements for plugins. */ - TSharedPtr PortalUserService; - - /** The portal user login service, to allow us to trigger a prompt to sign-in, if required. */ - TSharedPtr PortalUserLoginService; - - /** The current state of the plug-in auth pipeline. */ - EPluginAuthorizationState CurrentState; - - /** The entitlement result we may be waiting on. */ - TAsyncResult EntitlementResult; - - /** The result from the request for user details. */ - TAsyncResult UserDetailsResult; - - /** The result from requesting the user sign-in, they may sign-in, they may cancel. */ - TAsyncResult UserSigninResult; + /** The latest state of the plug-in authorization pipeline. */ + EPluginAuthorizationState AuthorizationState; /** If the user is authorized to us the plug-in, we'll call this function to alert the plug-in that everything is good to go. */ TFunction AuthorizedCallback; + + /** The executioner of the authorization pipeline. */ + TSharedPtr Authorizer; }; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp index fbe2e8446f0d..9e560d48ae96 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.cpp @@ -71,9 +71,12 @@ void SPropertyEditorEditInline::Construct( const FArguments& InArgs, const TShar { PropertyEditor = InPropertyEditor; + TWeakPtr WeakHandlePtr = InPropertyEditor->GetPropertyHandle(); + ChildSlot [ SAssignNew(ComboButton, SComboButton) + .IsEnabled(this, &SPropertyEditorEditInline::IsValueEnabled, WeakHandlePtr) .OnGetMenuContent(this, &SPropertyEditorEditInline::GenerateClassPicker) .ContentPadding(0) .ToolTipText(InPropertyEditor, &FPropertyEditor::GetValueAsText ) @@ -99,6 +102,16 @@ void SPropertyEditorEditInline::Construct( const FArguments& InArgs, const TShar ]; } +bool SPropertyEditorEditInline::IsValueEnabled(TWeakPtr WeakHandlePtr) const +{ + if (WeakHandlePtr.IsValid()) + { + return !WeakHandlePtr.Pin()->IsEditConst(); + } + + return false; +} + FText SPropertyEditorEditInline::GetDisplayValueAsString() const { UObject* CurrentValue = NULL; diff --git a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.h b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.h index 51c60d8a5879..50250a2054f0 100644 --- a/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.h +++ b/Engine/Source/Editor/PropertyEditor/Private/UserInterface/PropertyEditor/SPropertyEditorEditInline.h @@ -30,6 +30,15 @@ public: private: + /** + * Called to see if the value is enabled for editing + * + * @param WeakHandlePtr Handle to the property that the new value is for + * + * @return true if the property is enabled + */ + bool IsValueEnabled(TWeakPtr WeakHandlePtr) const; + /** * @return The current display value for the combo box as a string */ diff --git a/Engine/Source/Editor/SequenceRecorder/Private/ActorGroupDetailsCustomization.cpp b/Engine/Source/Editor/SequenceRecorder/Private/ActorGroupDetailsCustomization.cpp index fb0eaa7db0fd..438256c34ccf 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/ActorGroupDetailsCustomization.cpp +++ b/Engine/Source/Editor/SequenceRecorder/Private/ActorGroupDetailsCustomization.cpp @@ -52,7 +52,7 @@ void FActorGroupDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& Det SAssignNew(SequenceRecorderGroupNameTextBox, SEditableTextBox) .Text_Lambda([]() { - TWeakObjectPtr Group = FSequenceRecorder::Get().GetRecordingGroup(); + TWeakObjectPtr Group = FSequenceRecorder::Get().GetCurrentRecordingGroup(); if (Group.IsValid()) { return FText::FromName(Group->GroupName); @@ -70,8 +70,8 @@ void FActorGroupDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& Det // Check to see that no other profile is using this name. const FSequenceRecorder& SeqRec = FSequenceRecorder::Get(); - if( SeqRec.GetRecordingGroup().IsValid() && - SeqRec.GetRecordingGroup()->GroupName != ProfileAsName && + if( SeqRec.GetCurrentRecordingGroup().IsValid() && + SeqRec.GetCurrentRecordingGroup()->GroupName != ProfileAsName && SeqRec.GetRecordingGroupNames().Contains(ProfileAsName)) { SequenceRecorderGroupNameTextBox->SetError(FText::Format(LOCTEXT("GroupNameAlreadyExists", "Group '{0}' already exists"), InText)); @@ -178,7 +178,7 @@ TSharedRef FActorGroupDetailsCustomization::FillRecordingProfileOptions Action.GetActionCheckState = FGetActionCheckState::CreateLambda([GroupName]() { - TWeakObjectPtr ActiveGroup = FSequenceRecorder::Get().GetRecordingGroup(); + TWeakObjectPtr ActiveGroup = FSequenceRecorder::Get().GetCurrentRecordingGroup(); return (ActiveGroup.IsValid() && ActiveGroup->GroupName == GroupName) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; }); @@ -206,7 +206,7 @@ void FActorGroupDetailsCustomization::HandleRecordingGroupNameCommitted(const FT if (InCommitType != ETextCommit::OnCleared) { // This is a group re-name operation. - TWeakObjectPtr CurrentGroup = FSequenceRecorder::Get().GetRecordingGroup(); + TWeakObjectPtr CurrentGroup = FSequenceRecorder::Get().GetCurrentRecordingGroup(); if (CurrentGroup.IsValid()) { TArray ExistingGroupNames = FSequenceRecorder::Get().GetRecordingGroupNames(); diff --git a/Engine/Source/Editor/SequenceRecorder/Private/SSequenceRecorder.cpp b/Engine/Source/Editor/SequenceRecorder/Private/SSequenceRecorder.cpp index 7d114f19e5ab..fff21184ad2a 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/SSequenceRecorder.cpp +++ b/Engine/Source/Editor/SequenceRecorder/Private/SSequenceRecorder.cpp @@ -428,6 +428,7 @@ void SSequenceRecorder::Construct(const FArguments& Args) ActiveTimerHandle = RegisterActiveTimer(0.1f, FWidgetActiveTimerDelegate::CreateSP(this, &SSequenceRecorder::HandleRefreshItems)); } + FSequenceRecorder::Get().OnRecordingGroupAddedDelegate.AddRaw(this, &SSequenceRecorder::HandleRecordingGroupAddedToSequenceRecorder); #if WITH_EDITOR FEditorSupportDelegates::PrepareToCleanseEditorObject.AddRaw(this, &SSequenceRecorder::HandleMapUnload); #endif @@ -438,6 +439,8 @@ SSequenceRecorder::~SSequenceRecorder() #if WITH_EDITOR FEditorSupportDelegates::PrepareToCleanseEditorObject.RemoveAll(this); #endif + FSequenceRecorder::Get().OnRecordingGroupAddedDelegate.RemoveAll(this); + } void SSequenceRecorder::HandleMapUnload(UObject* Object) @@ -598,9 +601,9 @@ void SSequenceRecorder::HandleRemoveRecording() // Remove the recording from the current group here. We can't use the // FSequenceRecorder function as they are called when switching groups, // and not just when the user removes items. - if (FSequenceRecorder::Get().GetRecordingGroup().IsValid()) + if (FSequenceRecorder::Get().GetCurrentRecordingGroup().IsValid()) { - FSequenceRecorder::Get().GetRecordingGroup()->RecordedActors.Remove(SelectedRecording); + FSequenceRecorder::Get().GetCurrentRecordingGroup()->RecordedActors.Remove(SelectedRecording); } TArray> SelectedObjects = ActorRecordingDetailsView->GetSelectedObjects(); @@ -619,9 +622,9 @@ bool SSequenceRecorder::CanRemoveRecording() const void SSequenceRecorder::HandleRemoveAllRecordings() { FSequenceRecorder::Get().ClearQueuedRecordings(); - if (FSequenceRecorder::Get().GetRecordingGroup().IsValid()) + if (FSequenceRecorder::Get().GetCurrentRecordingGroup().IsValid()) { - FSequenceRecorder::Get().GetRecordingGroup()->RecordedActors.Empty(); + FSequenceRecorder::Get().GetCurrentRecordingGroup()->RecordedActors.Empty(); } ActorRecordingDetailsView->SetObject(nullptr); } @@ -644,9 +647,21 @@ EActiveTimerReturnType SSequenceRecorder::HandleRefreshItems(double InCurrentTim void SSequenceRecorder::HandleAddRecordingGroup() { - FSequenceRecorder::Get().AddRecordingGroup(); - check(FSequenceRecorder::Get().GetRecordingGroup().IsValid()); - RecordingGroupDetailsView->SetObject(FSequenceRecorder::Get().GetRecordingGroup().Get()); + TWeakObjectPtr ActorGroup = FSequenceRecorder::Get().AddRecordingGroup(); + check(ActorGroup.IsValid()); +} + +void SSequenceRecorder::HandleRecordingGroupAddedToSequenceRecorder(TWeakObjectPtr ActorGroup) +{ + if (ActorGroup.IsValid()) + { + RecordingGroupDetailsView->SetObject(ActorGroup.Get()); + } + else + { + // Fall back to the CDO in the event of an unexpected failure so the UI doesn't disappear. + RecordingGroupDetailsView->SetObject(GetMutableDefault()); + } } bool SSequenceRecorder::CanAddRecordingGroup() const @@ -659,9 +674,9 @@ void SSequenceRecorder::HandleLoadRecordingActorGroup(FName Name) FSequenceRecorder::Get().LoadRecordingGroup(Name); // Bind our details view to the newly loaded group. - if (FSequenceRecorder::Get().GetRecordingGroup().IsValid()) + if (FSequenceRecorder::Get().GetCurrentRecordingGroup().IsValid()) { - RecordingGroupDetailsView->SetObject(FSequenceRecorder::Get().GetRecordingGroup().Get()); + RecordingGroupDetailsView->SetObject(FSequenceRecorder::Get().GetCurrentRecordingGroup().Get()); } else { @@ -680,9 +695,9 @@ void SSequenceRecorder::HandleRemoveRecordingGroup() if (RecordingProfiles.Num() > 0) { FSequenceRecorder::Get().LoadRecordingGroup(RecordingProfiles[RecordingProfiles.Num() - 1]); - check(FSequenceRecorder::Get().GetRecordingGroup().Get()); + check(FSequenceRecorder::Get().GetCurrentRecordingGroup().Get()); - RecordingGroupDetailsView->SetObject(FSequenceRecorder::Get().GetRecordingGroup().Get()); + RecordingGroupDetailsView->SetObject(FSequenceRecorder::Get().GetCurrentRecordingGroup().Get()); } else { @@ -692,7 +707,7 @@ void SSequenceRecorder::HandleRemoveRecordingGroup() bool SSequenceRecorder::CanRemoveRecordingGroup() const { - TWeakObjectPtr RecordingGroup = FSequenceRecorder::Get().GetRecordingGroup(); + TWeakObjectPtr RecordingGroup = FSequenceRecorder::Get().GetCurrentRecordingGroup(); if (RecordingGroup.IsValid()) { return RecordingGroup->GroupName != NAME_None; @@ -708,7 +723,7 @@ void SSequenceRecorder::HandleDuplicateRecordingGroup() bool SSequenceRecorder::CanDuplicateRecordingGroup() const { - TWeakObjectPtr RecordingGroup = FSequenceRecorder::Get().GetRecordingGroup(); + TWeakObjectPtr RecordingGroup = FSequenceRecorder::Get().GetCurrentRecordingGroup(); if (RecordingGroup.IsValid()) { return RecordingGroup->GroupName != NAME_None; diff --git a/Engine/Source/Editor/SequenceRecorder/Private/SSequenceRecorder.h b/Engine/Source/Editor/SequenceRecorder/Private/SSequenceRecorder.h index 7115f2f6b98e..8e6721de4388 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/SSequenceRecorder.h +++ b/Engine/Source/Editor/SequenceRecorder/Private/SSequenceRecorder.h @@ -59,6 +59,7 @@ private: bool IsStopAllVisible() const; void HandleAddRecording(); + void HandleRecordingGroupAddedToSequenceRecorder(TWeakObjectPtr ActorGroup); bool CanAddRecording() const; diff --git a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.cpp b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.cpp index bbf224157556..1ceb6578e9f7 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.cpp +++ b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.cpp @@ -218,7 +218,7 @@ UActorRecording* FSequenceRecorder::AddNewQueuedRecording(AActor* Actor, UAnimSe ActorRecording->TargetAnimation = AnimSequence; ActorRecording->AnimationSettings.Length = Length; - TWeakObjectPtr RecordingGroup = GetRecordingGroup(); + TWeakObjectPtr RecordingGroup = GetCurrentRecordingGroup(); if (RecordingGroup.IsValid()) { ActorRecording->bCreateLevelSequence = RecordingGroup.Get()->bSpecifyTargetLevelSequence; @@ -659,7 +659,7 @@ bool FSequenceRecorder::StartRecordingInternal(UWorld* World) if(Settings->bCreateLevelSequence) { - TWeakObjectPtr RecordingGroup = GetRecordingGroup(); + TWeakObjectPtr RecordingGroup = GetCurrentRecordingGroup(); if (RecordingGroup.IsValid() && RecordingGroup.Get()->bSpecifyTargetLevelSequence && RecordingGroup.Get()->TargetLevelSequence != nullptr) { LevelSequence = RecordingGroup.Get()->TargetLevelSequence; @@ -1194,15 +1194,15 @@ TWeakObjectPtr FSequenceRecorder::GetRecordingGroupActor return CachedRecordingActor; } -void FSequenceRecorder::AddRecordingGroup() +TWeakObjectPtr FSequenceRecorder::AddRecordingGroup() { TWeakObjectPtr GroupActor = GetRecordingGroupActor(); UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); FDirectoryPath ExistingBasePath; - if (GetRecordingGroup().IsValid()) + if (GetCurrentRecordingGroup().IsValid()) { - ExistingBasePath = GetRecordingGroup().Get()->SequenceRecordingBasePath; + ExistingBasePath = GetCurrentRecordingGroup().Get()->SequenceRecordingBasePath; } // There may not be a group actor in the level yet, so we'll spawn a new one. @@ -1230,11 +1230,18 @@ void FSequenceRecorder::AddRecordingGroup() // And then select our new object by default CurrentRecorderGroup = ActorGroup; + + if (OnRecordingGroupAddedDelegate.IsBound()) + { + OnRecordingGroupAddedDelegate.Broadcast(CurrentRecorderGroup); + } + + return CurrentRecorderGroup; } void FSequenceRecorder::RemoveCurrentRecordingGroup() { - if (!GetRecordingGroup().IsValid()) + if (!GetCurrentRecordingGroup().IsValid()) { return; } @@ -1243,29 +1250,29 @@ void FSequenceRecorder::RemoveCurrentRecordingGroup() TWeakObjectPtr GroupActor = GetRecordingGroupActor(); if (GroupActor.IsValid()) { - GroupActor->ActorGroups.Remove(GetRecordingGroup().Get()); + GroupActor->ActorGroups.Remove(GetCurrentRecordingGroup().Get()); } } -void FSequenceRecorder::DuplicateRecordingGroup() +TWeakObjectPtr FSequenceRecorder::DuplicateRecordingGroup() { - check(GetRecordingGroup().IsValid()); + check(GetCurrentRecordingGroup().IsValid()); check(GetRecordingGroupActor().IsValid()); FString BaseName; - if (GetRecordingGroup().IsValid()) + if (GetCurrentRecordingGroup().IsValid()) { - BaseName = GetRecordingGroup().Get()->SequenceName; + BaseName = GetCurrentRecordingGroup().Get()->SequenceName; } - USequenceRecorderActorGroup* DuplicatedGroup = DuplicateObject(GetRecordingGroup().Get(), GetRecordingGroupActor().Get()); + USequenceRecorderActorGroup* DuplicatedGroup = DuplicateObject(GetCurrentRecordingGroup().Get(), GetRecordingGroupActor().Get()); FString NewName = SequenceRecorderUtils::MakeNewGroupName(*DuplicatedGroup->SequenceRecordingBasePath.Path, BaseName, GetRecordingGroupNames()); DuplicatedGroup->GroupName = FName(*NewName); DuplicatedGroup->SequenceName = NewName; GetRecordingGroupActor().Get()->ActorGroups.Add(DuplicatedGroup); // We'll invoke the standard load function so that it triggers everything to clear/update correctly. - LoadRecordingGroup(DuplicatedGroup->GroupName); + return LoadRecordingGroup(DuplicatedGroup->GroupName); } TArray FSequenceRecorder::GetRecordingGroupNames() const @@ -1294,7 +1301,7 @@ TArray FSequenceRecorder::GetRecordingGroupNames() const return GroupNames; } -void FSequenceRecorder::LoadRecordingGroup(const FName Name) +TWeakObjectPtr FSequenceRecorder::LoadRecordingGroup(const FName Name) { TWeakObjectPtr GroupActor = nullptr; UWorld* EditorWorld = GEditor->GetEditorWorldContext().World(); @@ -1331,7 +1338,7 @@ void FSequenceRecorder::LoadRecordingGroup(const FName Name) } } RefreshNextSequence(); - return; + return CurrentRecorderGroup; } } @@ -1340,11 +1347,12 @@ void FSequenceRecorder::LoadRecordingGroup(const FName Name) RefreshNextSequence(); ClearQueuedRecordings(); CurrentRecorderGroup = nullptr; + return nullptr; } FString FSequenceRecorder::GetSequenceRecordingBasePath() const { - TWeakObjectPtr RecordingGroup = GetRecordingGroup(); + TWeakObjectPtr RecordingGroup = GetCurrentRecordingGroup(); if (RecordingGroup.IsValid()) { return RecordingGroup->SequenceRecordingBasePath.Path; @@ -1356,7 +1364,7 @@ FString FSequenceRecorder::GetSequenceRecordingBasePath() const FString FSequenceRecorder::GetSequenceRecordingName() const { - TWeakObjectPtr RecordingGroup = GetRecordingGroup(); + TWeakObjectPtr RecordingGroup = GetCurrentRecordingGroup(); if (RecordingGroup.IsValid()) { return RecordingGroup->SequenceName; diff --git a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.h b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.h index 5ec5e0d7b72d..6fcb021e33b6 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.h +++ b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorder.h @@ -94,17 +94,17 @@ public: /** Handle actors being de-spawned */ void HandleActorDespawned(AActor* Actor); - TWeakObjectPtr GetRecordingGroup() const + TWeakObjectPtr GetCurrentRecordingGroup() const { return CurrentRecorderGroup; } TWeakObjectPtr GetRecordingGroupActor(); - void AddRecordingGroup(); + TWeakObjectPtr AddRecordingGroup(); void RemoveCurrentRecordingGroup(); - void DuplicateRecordingGroup(); - void LoadRecordingGroup(const FName Name); + TWeakObjectPtr DuplicateRecordingGroup(); + TWeakObjectPtr LoadRecordingGroup(const FName Name); TArray GetRecordingGroupNames() const; @@ -138,6 +138,9 @@ public: /** Multicast delegate fired when recording has finished */ FOnRecordingFinished OnRecordingFinishedDelegate; + /** Multicast delegate fired when a recording group has been added */ + FOnRecordingGroupAdded OnRecordingGroupAddedDelegate; + private: /** Starts recording a sequence, possibly delayed */ bool StartRecordingInternal(UWorld* World); diff --git a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderActorGroup.cpp b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderActorGroup.cpp new file mode 100644 index 000000000000..fa580fd9a2e8 --- /dev/null +++ b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderActorGroup.cpp @@ -0,0 +1,16 @@ +#include "SequenceRecorderActorGroup.h" +#include "SequenceRecorder.h" + +void USequenceRecorderActorGroup::PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) +{ + Super::PostEditChangeChainProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.Property) + { + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(USequenceRecorderActorGroup, SequenceName) || + PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(USequenceRecorderActorGroup, SequenceRecordingBasePath)) + { + FSequenceRecorder::Get().ForceRefreshNextSequence(); + } + } +} \ No newline at end of file diff --git a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderModule.cpp b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderModule.cpp index afbeefb3ef5c..6de2a48a00fe 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderModule.cpp +++ b/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderModule.cpp @@ -638,18 +638,20 @@ class FSequenceRecorderModule : public ISequenceRecorder, private FSelfRegisteri return AudioFactory ? AudioFactory() : TUniquePtr(); } - virtual void QueueActorToRecord(AActor* ActorToRecord) override + virtual UActorRecording* QueueActorToRecord(AActor* ActorToRecord) override { if (ActorToRecord && !FSequenceRecorder::Get().FindRecording(ActorToRecord)) { - FSequenceRecorder::Get().AddNewQueuedRecording(ActorToRecord); + return FSequenceRecorder::Get().AddNewQueuedRecording(ActorToRecord); } + + return nullptr; } virtual uint32 GetTakeNumberForActor(AActor* InActor) const override { // If not using a group, take numbers aren't in use, return 0 - if (!FSequenceRecorder::Get().GetRecordingGroup().IsValid()) + if (!FSequenceRecorder::Get().GetCurrentRecordingGroup().IsValid()) { return 0; } @@ -666,6 +668,9 @@ class FSequenceRecorderModule : public ISequenceRecorder, private FSelfRegisteri virtual FOnRecordingFinished& OnRecordingFinished() override { return FSequenceRecorder::Get().OnRecordingFinishedDelegate; } + virtual FOnRecordingGroupAdded& OnRecordingGroupAdded() override { return FSequenceRecorder::Get().OnRecordingGroupAddedDelegate; } + + virtual FString GetSequenceRecordingName() const override { return FSequenceRecorder::Get().GetSequenceRecordingName(); @@ -684,6 +689,42 @@ class FSequenceRecorderModule : public ISequenceRecorder, private FSelfRegisteri } } + /** Returns the current recording group (if any), otherwise returns nullptr. */ + virtual TWeakObjectPtr GetCurrentRecordingGroup() const override + { + return FSequenceRecorder::Get().GetCurrentRecordingGroup(); + } + + /** Adds a new recording group and picks a default name. Returns the new recording group and sets as the current recording group. */ + virtual TWeakObjectPtr AddRecordingGroup() override + { + return FSequenceRecorder::Get().AddRecordingGroup(); + } + + /** Removes the current recording group if any. Will make GetRecordingGroup() return nullptr. */ + virtual void RemoveCurrentRecordingGroup() override + { + return FSequenceRecorder::Get().RemoveCurrentRecordingGroup(); + } + + /** Duplicates the current recording group if any. Returns the new recording group and sets as the current recording group. */ + virtual TWeakObjectPtr DuplicateRecordingGroup() override + { + return FSequenceRecorder::Get().DuplicateRecordingGroup(); + } + + /** Attempts to load a recording group from the specified name. Returns a pointer to the group if successfully loaded, otherwise nullptr. */ + virtual TWeakObjectPtr LoadRecordingGroup(const FName Name) override + { + return FSequenceRecorder::Get().LoadRecordingGroup(Name); + } + + /** Returns a list of names for the recording groups stored in this map. */ + virtual TArray GetRecordingGroupNames() const override + { + return FSequenceRecorder::Get().GetRecordingGroupNames(); + } + #if WITH_EDITOR static UAnimSequence* HandleCaptureSingleFrameAnimSequence(USkeletalMeshComponent* Component) { diff --git a/Engine/Source/Editor/SequenceRecorder/Public/ISequenceRecorder.h b/Engine/Source/Editor/SequenceRecorder/Public/ISequenceRecorder.h index 15254ceb64ac..b849bf0422fa 100644 --- a/Engine/Source/Editor/SequenceRecorder/Public/ISequenceRecorder.h +++ b/Engine/Source/Editor/SequenceRecorder/Public/ISequenceRecorder.h @@ -7,6 +7,7 @@ #include "Modules/ModuleInterface.h" #include "Containers/ArrayView.h" #include "Misc/QualifiedFrameTime.h" +#include "SequenceRecorderActorGroup.h" class AActor; class ISequenceAudioRecorder; @@ -15,6 +16,8 @@ DECLARE_MULTICAST_DELEGATE_OneParam(FOnRecordingStarted, class UMovieSceneSequen DECLARE_MULTICAST_DELEGATE_OneParam(FOnRecordingFinished, class UMovieSceneSequence* /*Sequence*/); +DECLARE_MULTICAST_DELEGATE_OneParam(FOnRecordingGroupAdded, TWeakObjectPtr /*ActorGroup*/); + class ISequenceRecorder : public IModuleInterface { public: @@ -100,7 +103,7 @@ public: * Add an actor to be recorded when the next recording pass begins * @param ActorToRecord The actor to queue for recording */ - virtual void QueueActorToRecord(AActor* ActorToRecord) = 0; + virtual UActorRecording* QueueActorToRecord(AActor* ActorToRecord) = 0; /** * Get the take number of an actor that is queued to record in the current group @@ -127,4 +130,25 @@ public: /** Get the directory that the sequence should record into. */ virtual FString GetSequenceRecordingBasePath() const = 0; + + /** Returns the current recording group (if any), otherwise returns nullptr. */ + virtual TWeakObjectPtr GetCurrentRecordingGroup() const = 0; + + /** Adds a new recording group and picks a default name. Returns the new recording group and sets as the current recording group. */ + virtual TWeakObjectPtr AddRecordingGroup() = 0; + + /** Removes the current recording group if any. Will make GetRecordingGroup() return nullptr. */ + virtual void RemoveCurrentRecordingGroup() = 0; + + /** Duplicates the current recording group if any. Returns the new recording group and sets as the current recording group. */ + virtual TWeakObjectPtr DuplicateRecordingGroup() = 0; + + /** Attempts to load a recording group from the specified name. Returns a pointer to the group if successfully loaded, otherwise nullptr. */ + virtual TWeakObjectPtr LoadRecordingGroup(const FName Name) = 0; + + /** Returns a list of names for the recording groups stored in this map. */ + virtual TArray GetRecordingGroupNames() const = 0; + + /** Get the Recording Group added delegate */ + virtual FOnRecordingGroupAdded& OnRecordingGroupAdded() = 0; }; diff --git a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderActorGroup.h b/Engine/Source/Editor/SequenceRecorder/Public/SequenceRecorderActorGroup.h similarity index 83% rename from Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderActorGroup.h rename to Engine/Source/Editor/SequenceRecorder/Public/SequenceRecorderActorGroup.h index f9536bb5e6d8..87b1c5ea4a5c 100644 --- a/Engine/Source/Editor/SequenceRecorder/Private/SequenceRecorderActorGroup.h +++ b/Engine/Source/Editor/SequenceRecorder/Public/SequenceRecorderActorGroup.h @@ -3,10 +3,13 @@ #pragma once #include "CoreMinimal.h" -#include "GameFramework/Info.h" -#include "SequenceRecorder.h" +#include "Engine/EngineTypes.h" +#include "GameFramework/Actor.h" #include "SequenceRecorderActorGroup.generated.h" +// Forward Declares +class UActorRecording; + UCLASS() class SEQUENCERECORDER_API USequenceRecorderActorGroup : public UObject { @@ -47,20 +50,7 @@ public: UPROPERTY(VisibleAnywhere, Category = "Recording Groups") TArray RecordedActors; - void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override - { - Super::PostEditChangeChainProperty(PropertyChangedEvent); - - if (PropertyChangedEvent.Property) - { - if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(USequenceRecorderActorGroup, SequenceName) || - PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(USequenceRecorderActorGroup, SequenceRecordingBasePath)) - { - FSequenceRecorder::Get().ForceRefreshNextSequence(); - } - } - } - + void PostEditChangeChainProperty(struct FPropertyChangedChainEvent& PropertyChangedEvent) override; }; /** diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp index 544ebdde3e17..f9ca4b825d75 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp +++ b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.cpp @@ -97,7 +97,7 @@ void FStaticMeshEditor::RegisterTabSpawners(const TSharedRef& InTabManager->RegisterTabSpawner(PreviewSceneSettingsTabId, FOnSpawnTab::CreateSP(this, &FStaticMeshEditor::SpawnTab_PreviewSceneSettings)) .SetDisplayName(LOCTEXT("PreviewSceneTab", "Preview Scene Settings")) .SetGroup(WorkspaceMenuCategoryRef) - .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); + .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); } void FStaticMeshEditor::UnregisterTabSpawners(const TSharedRef& InTabManager) @@ -153,7 +153,7 @@ void FStaticMeshEditor::InitStaticMeshEditor( const EToolkitMode::Type Mode, con .ObjectToEdit(ObjectToEdit); FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked(TEXT("PropertyEditor")); - + FDetailsViewArgs DetailsViewArgs; DetailsViewArgs.bAllowSearch = true; DetailsViewArgs.bLockable = false; @@ -199,7 +199,7 @@ void FStaticMeshEditor::InitStaticMeshEditor( const EToolkitMode::Type Mode, con ->Split ( FTabManager::NewStack() - ->SetSizeCoefficient(0.7f) + ->SetSizeCoefficient(0.7f) ->AddTab(PreviewSceneSettingsTabId, ETabState::OpenedTab) ->AddTab(PropertiesTabId, ETabState::OpenedTab) ) @@ -217,7 +217,7 @@ void FStaticMeshEditor::InitStaticMeshEditor( const EToolkitMode::Type Mode, con const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, StaticMeshEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultToolbar, bCreateDefaultStandaloneMenu, ObjectToEdit ); - + ExtendMenu(); ExtendToolBar(); RegenerateMenusAndToolbars(); @@ -256,7 +256,7 @@ void FStaticMeshEditor::ExtendMenu() InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().FindSource); } InMenuBuilder.EndSection(); - + InMenuBuilder.BeginSection("MeshChange"); { InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().ChangeMesh); @@ -280,7 +280,7 @@ void FStaticMeshEditor::ExtendMenu() InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP10Y); InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP10Z); InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP18); - InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP26); + InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().CreateDOP26); InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().ConvertBoxesToConvex); InMenuBuilder.AddMenuEntry(FStaticMeshEditorCommands::Get().RemoveCollision); InMenuBuilder.AddMenuEntry(FGenericCommands::Get().Delete, "DeleteCollision", LOCTEXT("DeleteCollision", "Delete Selected Collision"), LOCTEXT("DeleteCollisionToolTip", "Deletes the selected Collision from the mesh.")); @@ -316,7 +316,7 @@ void FStaticMeshEditor::ExtendMenu() "Collision"); } }; - + TSharedPtr MenuExtender = MakeShareable(new FExtender); MenuExtender->AddMenuExtension( @@ -331,9 +331,9 @@ void FStaticMeshEditor::ExtendMenu() GetToolkitCommands(), FMenuBarExtensionDelegate::CreateStatic( &Local::GenerateMeshAndCollisionMenuBars ) ); - + AddMenuExtender(MenuExtender); - + IStaticMeshEditorModule* StaticMeshEditorModule = &FModuleManager::LoadModuleChecked( "StaticMeshEditor" ); AddMenuExtender(StaticMeshEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); } @@ -346,7 +346,7 @@ void FStaticMeshEditor::AddReferencedObjects( FReferenceCollector& Collector ) TSharedRef FStaticMeshEditor::SpawnTab_Viewport( const FSpawnTabArgs& Args ) { check( Args.GetTabId() == ViewportTabId ); - + TSharedRef SpawnedTab = SNew(SDockTab) .Label( LOCTEXT("StaticMeshViewport_TabTitle", "Viewport") ) @@ -413,10 +413,10 @@ void FStaticMeshEditor::BindCommands() FExecuteAction::CreateSP( this, &FStaticMeshEditor::DeleteSelected ), FCanExecuteAction::CreateSP(this, &FStaticMeshEditor::CanDeleteSelected)); - UICommandList->MapAction( FGenericCommands::Get().Undo, + UICommandList->MapAction( FGenericCommands::Get().Undo, FExecuteAction::CreateSP( this, &FStaticMeshEditor::UndoAction ) ); - UICommandList->MapAction( FGenericCommands::Get().Redo, + UICommandList->MapAction( FGenericCommands::Get().Redo, FExecuteAction::CreateSP( this, &FStaticMeshEditor::RedoAction ) ); UICommandList->MapAction( @@ -514,7 +514,7 @@ void FStaticMeshEditor::ExtendToolBar() { struct Local { - static void FillToolbar(FToolBarBuilder& ToolbarBuilder, FStaticMeshEditor* ThisEditor) + static void FillToolbar(FToolBarBuilder& ToolbarBuilder, FStaticMeshEditor* ThisEditor) { auto ConstructReimportContextMenu = [ThisEditor]() { @@ -579,15 +579,15 @@ void FStaticMeshEditor::ExtendToolBar() FOnGetContent OnGetUVMenuContent = FOnGetContent::CreateRaw(ThisEditor, &FStaticMeshEditor::GenerateUVChannelComboList); ToolbarBuilder.AddComboButton( - FUIAction(), - OnGetUVMenuContent, - LOCTEXT("UVToolbarText", "UV"), + FUIAction(), + OnGetUVMenuContent, + LOCTEXT("UVToolbarText", "UV"), LOCTEXT("UVToolbarTooltip", "Toggles display of the static mesh's UVs for the specified channel."), FSlateIcon(FEditorStyle::GetStyleSetName(), "StaticMeshEditor.SetDrawUVs")); } ToolbarBuilder.EndSection(); - + ToolbarBuilder.BeginSection("Camera"); { ToolbarBuilder.AddToolBarButton(FStaticMeshEditorCommands::Get().ResetCamera); @@ -608,11 +608,11 @@ void FStaticMeshEditor::ExtendToolBar() "Asset", EExtensionHook::After, Viewport->GetCommandList(), - FToolBarExtensionDelegate::CreateStatic(&Local::FillToolbar, ThisEditor) + FToolBarExtensionDelegate::CreateStatic(&Local::FillToolbar, ThisEditor) ); - + AddToolbarExtender(ToolbarExtender); - + IStaticMeshEditorModule* StaticMeshEditorModule = &FModuleManager::LoadModuleChecked( "StaticMeshEditor" ); AddToolbarExtender(StaticMeshEditorModule->GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); } @@ -625,7 +625,7 @@ void FStaticMeshEditor::BuildSubTools() SAssignNew( ConvexDecomposition, SConvexDecomposition ) .StaticMeshEditorPtr(SharedThis(this)); - + FAdvancedPreviewSceneModule& AdvancedPreviewSceneModule = FModuleManager::LoadModuleChecked("AdvancedPreviewScene"); AdvancedPreviewSettingsWidget = AdvancedPreviewSceneModule.CreateAdvancedPreviewSceneSettingsWidget(Viewport->GetPreviewScene()); } @@ -717,7 +717,7 @@ void FStaticMeshEditor::AddSelectedPrim(const FPrimData& InPrimData, bool bClear { ClearSelectedPrims(); } - SelectedPrims.Add(InPrimData); + SelectedPrims.Add(InPrimData); } void FStaticMeshEditor::RemoveSelectedPrim(const FPrimData& InPrimData) @@ -770,7 +770,7 @@ void FStaticMeshEditor::DuplicateSelectedPrims(const FVector* InOffset) switch (PrimData.PrimType) { case EAggCollisionShape::Sphere: - { + { const FKSphereElem SphereElem = AggGeom->SphereElems[PrimData.PrimIndex]; PrimData.PrimIndex = AggGeom->SphereElems.Add(SphereElem); } @@ -1143,7 +1143,7 @@ TSharedRef FStaticMeshEditor::GenerateUVChannelComboList() FUIAction DrawUVsAction; - FStaticMeshEditorViewportClient& ViewportClient = GetViewportClient(); + FStaticMeshEditorViewportClient& ViewportClient = Viewport->GetViewportClient(); DrawUVsAction.ExecuteAction = FExecuteAction::CreateRaw(&ViewportClient, &FStaticMeshEditorViewportClient::SetDrawUVOverlay, false); @@ -1186,7 +1186,7 @@ TSharedRef FStaticMeshEditor::GenerateUVChannelComboList() } -void FStaticMeshEditor::UpdateLODStats(int32 CurrentLOD) +void FStaticMeshEditor::UpdateLODStats(int32 CurrentLOD) { NumTriangles[CurrentLOD] = 0; //-V781 NumVertices[CurrentLOD] = 0; //-V781 @@ -1523,7 +1523,7 @@ void FStaticMeshEditor::OnCopyCollisionFromSelectedStaticMesh() // Invalidate physics data and create new meshes BodySetup->InvalidatePhysicsData(); - BodySetup->CreatePhysicsMeshes(); + BodySetup->CreatePhysicsMeshes(); GEditor->EndTransaction(); @@ -1784,7 +1784,7 @@ void FStaticMeshEditor::DeleteSelected() { DeleteSelectedSockets(); } - + if (HasSelectedPrims()) { DeleteSelectedPrims(); @@ -1940,12 +1940,7 @@ EViewModeIndex FStaticMeshEditor::GetViewMode() const } } -FStaticMeshEditorViewportClient& FStaticMeshEditor::GetViewportClient() -{ - return Viewport->GetViewportClient(); -} - -const FStaticMeshEditorViewportClient& FStaticMeshEditor::GetViewportClient() const +FEditorViewportClient& FStaticMeshEditor::GetViewportClient() { return Viewport->GetViewportClient(); } @@ -2057,12 +2052,12 @@ void FStaticMeshEditor::OnPostReimport(UObject* InObject, bool bSuccess) void FStaticMeshEditor::SetCurrentViewedUVChannel(int32 InNewUVChannel) { CurrentViewedUVChannel = FMath::Clamp(InNewUVChannel, 0, GetNumUVChannels()); - GetViewportClient().SetDrawUVOverlay(true); + Viewport->GetViewportClient().SetDrawUVOverlay(true); } ECheckBoxState FStaticMeshEditor::GetUVChannelCheckState(int32 TestUVChannel) const { - return CurrentViewedUVChannel == TestUVChannel && GetViewportClient().IsDrawUVOverlayChecked() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; + return CurrentViewedUVChannel == TestUVChannel && Viewport->GetViewportClient().IsDrawUVOverlayChecked() ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; } void FStaticMeshEditor::Tick(float DeltaTime) diff --git a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.h b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.h index ddfd97ea6446..7dd338c1e780 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.h +++ b/Engine/Source/Editor/StaticMeshEditor/Private/StaticMeshEditor.h @@ -21,6 +21,7 @@ class IDecomposeMeshToHullsAsync; #endif class FStaticMeshDetails; +class FEditorViewportClient; class IDetailsView; class SAdvancedPreviewDetailsTab; class SConvexDecomposition; @@ -121,6 +122,8 @@ public: virtual void DoDecomp(uint32 InHullCount, int32 InMaxHullVerts, uint32 InHullPrecision) override; virtual TSet< int32 >& GetSelectedEdges() override; + + virtual FEditorViewportClient& GetViewportClient() override; // End of IStaticMeshEditor /** Extends the toolbar menu to include static mesh editor options */ @@ -164,9 +167,6 @@ public: } } - class FStaticMeshEditorViewportClient& GetViewportClient(); - const class FStaticMeshEditorViewportClient& GetViewportClient() const; - /** For asynchronous convex decomposition support, this class is tickable in the editor to be able to confirm that the process is completed */ virtual bool IsTickableInEditor() const final @@ -368,7 +368,7 @@ private: TArray NumUVChannels; /** Delegates called after an undo operation for child widgets to refresh */ - FOnPostUndoMulticaster OnPostUndo; + FOnPostUndoMulticaster OnPostUndo; /** Information on the selected collision primitives */ TArray SelectedPrims; diff --git a/Engine/Source/Editor/StaticMeshEditor/Public/IStaticMeshEditor.h b/Engine/Source/Editor/StaticMeshEditor/Public/IStaticMeshEditor.h index 02768e2ad581..32702c4a7526 100644 --- a/Engine/Source/Editor/StaticMeshEditor/Public/IStaticMeshEditor.h +++ b/Engine/Source/Editor/StaticMeshEditor/Public/IStaticMeshEditor.h @@ -43,7 +43,7 @@ public: /** Called after an undo is performed to give child widgets a chance to refresh. */ DECLARE_MULTICAST_DELEGATE( FOnPostUndoMulticaster ); - // Post undo + // Post undo typedef FOnPostUndoMulticaster::FDelegate FOnPostUndo; /** Retrieves the current static mesh displayed in the Static Mesh Editor. */ @@ -55,8 +55,8 @@ public: /** Retrieves the currently selected socket from the Socket Manager. */ virtual UStaticMeshSocket* GetSelectedSocket() const = 0; - /** - * Set the currently selected socket in the Socket Manager. + /** + * Set the currently selected socket in the Socket Manager. * * @param InSelectedSocket The selected socket to pass on to the Socket Manager. */ @@ -68,7 +68,7 @@ public: /** Requests to rename selected socket */ virtual void RequestRenameSelectedSocket() = 0; - /** + /** * Checks to see if the prim data is valid compared with the static mesh * * @param InPrimData The data to check @@ -78,7 +78,7 @@ public: /** Checks to see if any prims are selected */ virtual bool HasSelectedPrims() const = 0; - /** + /** * Adds primitive information to the selected prims list * * @param InPrimData The data to add @@ -86,7 +86,7 @@ public: */ virtual void AddSelectedPrim(const FPrimData& InPrimData, bool bClearSelection) = 0; - /** + /** * Removes primitive information to the selected prims list * * @param InPrimData The data to remove @@ -96,7 +96,7 @@ public: /** Removes all invalid primitives from the list */ virtual void RemoveInvalidPrims() = 0; - /** + /** * Checks to see if the parsed primitive data is selected * * @param InPrimData The data to compare @@ -167,23 +167,23 @@ public: */ virtual void SetPrimTransform(const FPrimData& InPrimData, const FTransform& InPrimTransform) const = 0; - /** - * Retrieves the number of triangles in the current static mesh or it's forced LOD. + /** + * Retrieves the number of triangles in the current static mesh or it's forced LOD. * * @param LODLevel The desired LOD to retrieve the number of triangles for. * @returns The number of triangles for the specified LOD level. */ virtual int32 GetNumTriangles(int32 LODLevel = 0) const = 0; - /** - * Retrieves the number of vertices in the current static mesh or it's forced LOD. + /** + * Retrieves the number of vertices in the current static mesh or it's forced LOD. * - * @param LODLevel The desired LOD to retrieve the number of vertices for. + * @param LODLevel The desired LOD to retrieve the number of vertices for. * @returns The number of vertices for the specified LOD level. */ virtual int32 GetNumVertices(int32 LODLevel = 0) const = 0; - /** + /** * Retrieves the number of UV channels available. * * @param LODLevel The desired LOD to retrieve the number of UV channels for. @@ -246,6 +246,9 @@ public: /* Broadcast when selected LOD changes */ virtual void BroadcastOnSelectedLODChanged() = 0; + + /** Get the Static Mesh Editor's the viewport client */ + virtual class FEditorViewportClient& GetViewportClient() = 0; }; diff --git a/Engine/Source/Programs/nDisplayLauncher/App.config b/Engine/Source/Programs/nDisplayLauncher/App.config new file mode 100644 index 000000000000..eb922985a7a6 --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/App.config @@ -0,0 +1,6 @@ + + + + + + diff --git a/Engine/Source/Programs/nDisplayLauncher/App.xaml b/Engine/Source/Programs/nDisplayLauncher/App.xaml new file mode 100644 index 000000000000..3005ede1486d --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/App.xaml @@ -0,0 +1,282 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Engine/Source/Programs/nDisplayLauncher/App.xaml.cs b/Engine/Source/Programs/nDisplayLauncher/App.xaml.cs new file mode 100644 index 000000000000..4f3352a6058b --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/App.xaml.cs @@ -0,0 +1,71 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Configuration; +using System.Data; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace nDisplayLauncher +{ + /// + /// Interaction logic for App.xaml + /// + public partial class App : Application + { + protected override void OnStartup(StartupEventArgs e) + { + EventManager.RegisterClassHandler(typeof(TextBox), UIElement.PreviewMouseLeftButtonDownEvent, + new MouseButtonEventHandler(SelectivelyHandleMouseButton), true); + EventManager.RegisterClassHandler(typeof(TextBox), UIElement.GotKeyboardFocusEvent, + new RoutedEventHandler(SelectAllText), true); + + ParseCommandLine(e.Args); + + base.OnStartup(e); + } + + private static void SelectivelyHandleMouseButton(object sender, MouseButtonEventArgs e) + { + var textbox = (sender as TextBox); + if (textbox != null && !textbox.IsKeyboardFocusWithin) + { + if (e.OriginalSource.GetType().Name == "TextBoxView") + { + e.Handled = true; + textbox.Focus(); + } + } + } + + private static void SelectAllText(object sender, RoutedEventArgs e) + { + var textBox = e.OriginalSource as TextBox; + if (textBox != null) + textBox.SelectAll(); + } + + + private void ParseCommandLine(string[] args) + { + foreach (string arg in args) + { + try + { + if (arg.StartsWith(Runner.ArgListenerPort, StringComparison.OrdinalIgnoreCase)) + { + Runner.DefaultListenerPort = int.Parse(arg.Substring(Runner.ArgListenerPort.Length)); + return; + } + } + catch (Exception) + { + } + } + } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/AppLog/AppLogger.cs b/Engine/Source/Programs/nDisplayLauncher/AppLog/AppLogger.cs new file mode 100644 index 000000000000..070c086301b7 --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/AppLog/AppLogger.cs @@ -0,0 +1,69 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.ComponentModel; + + +namespace nDisplayLauncher +{ + public class AppLogger : INotifyPropertyChanged + { + private AppLogger() + { + + } + + //Implementation of INotifyPropertyChanged method for TwoWay binding + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnNotifyPropertyChanged(string propertyName) + { + if (PropertyChanged != null) + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + + //Set property with OnNotifyPropertyChanged call + protected void Set(ref T field, T newValue, string propertyName) + { + field = newValue; + OnNotifyPropertyChanged(propertyName); + } + + private static AppLogger _Instance; + public static AppLogger Instance + { + get + { + if (_Instance == null) + { + _Instance = new AppLogger(); + } + return _Instance; + } + } + + private string _Log; + public string Log + { + get + { + if (_Log == null) + { + _Log = string.Empty; + } + return _Log; + } + set { Set(ref _Log, value, "Log"); } + } + + public static void CleanLog() + { + Instance.Log = DateTime.Now.ToString() + System.Environment.NewLine; + } + + public static void Add(string text) + { + Instance.Log = Instance.Log + DateTime.Now.ToString() + ": " + text + System.Environment.NewLine; + } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/BaseInput.cs b/Engine/Source/Programs/nDisplayLauncher/Config/BaseInput.cs new file mode 100644 index 000000000000..51277ec43314 --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/BaseInput.cs @@ -0,0 +1,91 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nDisplayLauncher.Config +{ + public enum InputDeviceType + { + tracker, + analog, + buttons + } + + public class BaseInput : ConfigItem, IDataErrorInfo + { + + public string id { get; set; } + public InputDeviceType type { get; set; } + public string address { get; set; } + + public BaseInput() + { + id = "InputId"; + address = "InputName@127.0.0.1"; + } + + public BaseInput(string _id, InputDeviceType _type, string _address) + { + id = _id; + type = _type; + address = _address; + } + + //Implementation IDataErrorInfo methods for validation + public string this[string columnName] + { + get + { + string error = String.Empty; + if (columnName == "id" || columnName == validationName) + { + if (!ValidationRules.IsName(id)) + { + error = "Input ID should contain only letters, numbers and _"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "address" || columnName == validationName) + { + if (!ValidationRules.IsAddress(address)) + { + error = "Input Address in format InputName@127.0.0.1"; + AppLogger.Add("ERROR! " + error); + } + } + MainWindow.ConfigModifyIndicator(); + return error; + } + } + public string Error + { + get { throw new NotImplementedException(); } + } + + public override bool Validate() + { + bool isValid = ValidationRules.IsName(id) && ValidationRules.IsAddress(address); + if (!isValid) + { + AppLogger.Add("ERROR! Errors in Input [" + id + "]"); + string a = this[validationName]; + + } + + return isValid; + } + + public override string CreateCfg() + { + string stringCfg = "[input] "; + stringCfg = string.Concat(stringCfg, "id=", id, " type=", type.ToString(), " addr=", address, "\n"); + + return stringCfg; + } + } +} \ No newline at end of file diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/Camera.cs b/Engine/Source/Programs/nDisplayLauncher/Config/Camera.cs new file mode 100644 index 000000000000..18d54f12812e --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/Camera.cs @@ -0,0 +1,18 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nDisplayLauncher.Config +{ + class Camera + { + public string id { get; set; } + public string locationX { get; set; } + public string locationY { get; set; } + public string locationZ { get; set; } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/ClusterNode.cs b/Engine/Source/Programs/nDisplayLauncher/Config/ClusterNode.cs new file mode 100644 index 000000000000..751e182a398d --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/ClusterNode.cs @@ -0,0 +1,198 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows; +using System.ComponentModel; + +namespace nDisplayLauncher.Config +{ + public class ClusterNode : ConfigItem, IDataErrorInfo + { + + public string id { get; set; } + public bool isMaster { get; set; } + public string address { get; set; } + public Screen screen { get; set; } + public Viewport viewport { get; set; } + public string camera { get; set; } + public bool isWindowed { get; set; } + public string winX { get; set; } + public string winY { get; set; } + public string resX { get; set; } + public string resY { get; set; } + public ClusterNode() + { + id = "ClusterNodeId"; + address = "127.0.0.1"; + screen = null; + viewport = null; + camera = string.Empty; + isMaster = false; + isWindowed = false; + winX = string.Empty; + winY = string.Empty; + resX = string.Empty; + resY = string.Empty; + } + + public ClusterNode(string _id, string _address, Screen _screen, Viewport _viewport, string _camera, bool _isMaster) + { + id = _id; + address = _address; + screen = _screen; + viewport = _viewport; + camera = _camera; + isMaster = _isMaster; + isWindowed = false; + winX = string.Empty; + winY = string.Empty; + resX = string.Empty; + resY = string.Empty; + } + + public ClusterNode(string _id, string _address, Screen _screen, Viewport _viewport, string _camera, bool _isMaster, bool _isWindowed, string _winX, string _winY, string _resX, string _resY) + { + id = _id; + address = _address; + screen = _screen; + viewport = _viewport; + camera = _camera; + isMaster = _isMaster; + isWindowed = _isWindowed; + winX = _winX; + winY = _winY; + resX = _resX; + resY = _resY; + } + + //Implementation IDataErrorInfo methods for validation + public string this[string columnName] + { + get + { + string error = String.Empty; + if (columnName == "id" || columnName == validationName) + { + if (!ValidationRules.IsName(id)) + { + error = "Cluster node ID should contain only letters, numbers and _"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "address" || columnName == validationName) + { + if (!ValidationRules.IsIp(address)) + { + error = "Cluster node address should be IP address"; + AppLogger.Add("ERROR! " + error); + } + } + + if (columnName == "winX" || columnName == validationName) + { + if (isWindowed == true) + { + if (!ValidationRules.IsInt(winX.ToString())) + { + error = "x should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + } + if (columnName == "winY" || columnName == validationName) + { + if (isWindowed == true) + { + if (!ValidationRules.IsInt(winY.ToString())) + { + error = "y should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + } + if (columnName == "resX" || columnName == validationName) + { + if (isWindowed == true) + { + if (!ValidationRules.IsInt(resX.ToString()) || Convert.ToInt32(resX) < 0) + { + error = "Width should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + } + + if (columnName == "resY" || columnName == validationName) + { + if (isWindowed == true) + { + if (!ValidationRules.IsInt(resY.ToString()) || Convert.ToInt32(resY) < 0) + { + error = "Height should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + } + + MainWindow.ConfigModifyIndicator(); + return error; + } + } + public string Error + { + get { throw new NotImplementedException(); } + } + + public override bool Validate() + { + bool isValid = ValidationRules.IsName(id) && ValidationRules.IsIp(address); + if (!isValid) + { + AppLogger.Add("ERROR! Errors in Cluster Node [" + id + "]"); + string a = this[validationName]; + + } + + return isValid; + } + + public override string CreateCfg() + { + string stringCfg = "[cluster_node] "; + stringCfg = string.Concat(stringCfg, "id=", id, " addr=", address); + if (screen != null) + { + stringCfg = string.Concat(stringCfg, " screen=", screen.id); + } + if (viewport != null) + { + + stringCfg = string.Concat(stringCfg, " viewport=", viewport.id); + } + + if (isWindowed) + { + if (string.IsNullOrEmpty(winX)) winX = "0"; + if (string.IsNullOrEmpty(winY)) winY = "0"; + if (string.IsNullOrEmpty(resX)) resX = "0"; + if (string.IsNullOrEmpty(resY)) resY = "0"; + stringCfg = string.Concat(stringCfg, " windowed=true ", " WinX=", winX, " WinY=", winY, " ResX=", resX, " ResY=", resY); + } + + if (isMaster) + { + MainWindow Win = (MainWindow)Application.Current.MainWindow; + string portCS = Win.CurrentConfig.portCs; + string portSS = Win.CurrentConfig.portSs; + stringCfg = string.Concat(stringCfg, " port_cs=", portCS, " port_ss=", portSS, " master=true"); + } + stringCfg = string.Concat(stringCfg, "\n"); + return stringCfg; + } + + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/ConfigItem.cs b/Engine/Source/Programs/nDisplayLauncher/Config/ConfigItem.cs new file mode 100644 index 000000000000..fff3fdadb8a4 --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/ConfigItem.cs @@ -0,0 +1,18 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nDisplayLauncher.Config +{ + public abstract class ConfigItem + { + public string validationName = "Object validation"; + + public abstract string CreateCfg(); + public abstract bool Validate(); + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/DynamicCamera.cs b/Engine/Source/Programs/nDisplayLauncher/Config/DynamicCamera.cs new file mode 100644 index 000000000000..71cea2972a7e --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/DynamicCamera.cs @@ -0,0 +1,16 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nDisplayLauncher.Config +{ + class DynamicCamera : Camera + { + public BaseInput tracker { get; set; } + public string trackerChannel { get; set; } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/MasterNode.cs b/Engine/Source/Programs/nDisplayLauncher/Config/MasterNode.cs new file mode 100644 index 000000000000..cc370e87608a --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/MasterNode.cs @@ -0,0 +1,17 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nDisplayLauncher.Config +{ + class MasterNode + { + public ClusterNode masterNode { get; set; } + public string portCs { get; set; } + public string portSs { get; set; } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/Parser.cs b/Engine/Source/Programs/nDisplayLauncher/Config/Parser.cs new file mode 100644 index 000000000000..daed67df8ad5 --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/Parser.cs @@ -0,0 +1,140 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using nDisplayLauncher.Settings; +using nDisplayLauncher.Config; + +namespace nDisplayLauncher +{ + public static class Parser + { + //Config file parser + public static VRConfig Parse(string filePath, VRConfig currentConfig) + { + // refactoring needed + List inputLines = new List(); + List sceneNodeLines = new List(); + List screenLines = new List(); + List viewportLines = new List(); + List clusterNodeLines = new List(); + List cameraLines = new List(); + List generalLines = new List(); + List stereoLines = new List(); + List debugLines = new List(); + try + { + foreach (string line in File.ReadLines(filePath)) + { + if (line == string.Empty || line.First() == '#') + { + //Do nothing + } + else + { + if (line.ToLower().Contains("[input]")) + { + inputLines.Add(line); + } + if (line.ToLower().Contains("[scene_node]")) + { + sceneNodeLines.Add(line); + } + if (line.ToLower().Contains("[screen]")) + { + screenLines.Add(line); + } + if (line.ToLower().Contains("[viewport]")) + { + viewportLines.Add(line); + } + if (line.ToLower().Contains("[cluster_node]")) + { + clusterNodeLines.Add(line); + } + if (line.ToLower().Contains("[camera]")) + { + cameraLines.Add(line); + } + if (line.ToLower().Contains("[general]")) + { + generalLines.Add(line); + } + if (line.ToLower().Contains("[stereo]")) + { + stereoLines.Add(line); + } + if (line.ToLower().Contains("[debug]")) + { + debugLines.Add(line); + } + if (line.ToLower().Contains("[render]")) + { + //todo + } + if (line.ToLower().Contains("[custom]")) + { + //todo + } + } + } + foreach (string line in viewportLines) + { + currentConfig.ViewportParse(line); + } + foreach (string line in generalLines) + { + currentConfig.GeneralParse(line); + } + foreach (string line in stereoLines) + { + currentConfig.StereoParse(line); + } + foreach (string line in debugLines) + { + currentConfig.DebugParse(line); + } + foreach (string line in inputLines) + { + currentConfig.InputsParse(line); + } + foreach (string line in cameraLines) + { + currentConfig.CameraParse(line); + } + foreach (string line in sceneNodeLines) + { + currentConfig.SceneNodeParse(line); + } + foreach (string line in screenLines) + { + currentConfig.ScreenParse(line); + } + foreach (string line in clusterNodeLines) + { + currentConfig.ClusterNodeParse(line); + } + + currentConfig.sceneNodesView = currentConfig.ConvertSceneNodeList(currentConfig.sceneNodes); + currentConfig.name = Path.GetFileNameWithoutExtension(filePath); + //AppLogger.Add("Config " + currentConfig.name + " loaded"); + RegistrySaver.AddRegistryValue(RegistrySaver.RegConfigName, filePath); + + } + catch (FileNotFoundException) + { + AppLogger.Add("ERROR! Config " + currentConfig.name + "not found!"); + } + catch (System.ArgumentException) + { + AppLogger.Add("ERROR! Config " + currentConfig.name + "not found!"); + } + + return currentConfig; + } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/SceneNode.cs b/Engine/Source/Programs/nDisplayLauncher/Config/SceneNode.cs new file mode 100644 index 000000000000..9ef260ab6874 --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/SceneNode.cs @@ -0,0 +1,174 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nDisplayLauncher.Config +{ + public class SceneNode : ConfigItem, IDataErrorInfo + { + public string id { get; set; } + //public string nodeType { get; set; } + public string locationX { get; set; } + public string locationY { get; set; } + public string locationZ { get; set; } + public string rotationP { get; set; } + public string rotationY { get; set; } + public string rotationR { get; set; } + public TrackerInput tracker { get; set; } + public string trackerCh { get; set; } + + public SceneNode parent { get; set; } + + public SceneNode() + { + id = "SceneNodeId"; + locationX = "0"; + locationY = "0"; + locationZ = "0"; + rotationP = "0"; + rotationR = "0"; + rotationY = "0"; + trackerCh = "0"; + tracker = new TrackerInput(); + parent = null; + } + + public SceneNode(string _id, string _locationX, string _locationY, string _locationZ, string _rotationP, string _rotationY, string _rotationR, TrackerInput _tracker, string _trackerCh, SceneNode _parent) + { + id = _id; + locationX = _locationX; + locationY = _locationY; + locationZ = _locationZ; + rotationP = _rotationP; + rotationY = _rotationY; + rotationR = _rotationR; + tracker = _tracker; + trackerCh = _trackerCh; + parent = _parent; + } + + //Implementation IDataErrorInfo methods for validation + public string this[string columnName] + { + get + { + string error = String.Empty; + if (columnName == "id" || columnName == validationName) + { + if (!ValidationRules.IsName(id)) + { + error = "Scene Nodes ID should contain only letters, numbers and _"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationX" || columnName == validationName) + { + if (!ValidationRules.IsFloatNullable(locationX.ToString())) + { + error = "Location X should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationY" || columnName == validationName) + { + if (!ValidationRules.IsFloatNullable(locationY.ToString())) + { + error = "Location Y should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationZ" || columnName == validationName) + { + if (!ValidationRules.IsFloatNullable(locationZ.ToString())) + { + error = "Location Z should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationP" || columnName == validationName) + { + if (!ValidationRules.IsFloatNullable(rotationP.ToString())) + { + error = "Pitch should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationY" || columnName == validationName) + { + if (!ValidationRules.IsFloatNullable(rotationY.ToString())) + { + error = "Yaw should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationR" || columnName == validationName) + { + if (!ValidationRules.IsFloatNullable(rotationR.ToString())) + { + error = "Roll should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "trackerCh" || columnName == validationName) + { + if (!ValidationRules.IsIntNullable(trackerCh)) + { + error = "Tracker channel should be a positive integer"; + AppLogger.Add("ERROR! " + error); + } + } + + MainWindow.ConfigModifyIndicator(); + return error; + } + } + public string Error + { + get { throw new NotImplementedException(); } + } + + public override bool Validate() + { + bool isValid = ValidationRules.IsName(id) && ValidationRules.IsFloatNullable(locationX.ToString()) && ValidationRules.IsFloatNullable(locationY.ToString()) + && ValidationRules.IsFloatNullable(locationZ.ToString()) && ValidationRules.IsFloatNullable(rotationP.ToString()) + && ValidationRules.IsFloatNullable(rotationY.ToString()) && ValidationRules.IsFloatNullable(rotationR.ToString()); + if (!isValid) + { + AppLogger.Add("ERROR! Errors in Scene Node [" + id + "]"); + string a = this[validationName]; + + } + + return isValid; + } + + public override string CreateCfg() + { + string stringCfg = "[scene_node] "; + stringCfg = string.Concat(stringCfg, "id=", id); + if (!string.IsNullOrEmpty(locationX) && !string.IsNullOrEmpty(locationY) && !string.IsNullOrEmpty(locationZ) + && !string.IsNullOrEmpty(rotationP) && !string.IsNullOrEmpty(rotationY) && !string.IsNullOrEmpty(rotationR)) + { + stringCfg = string.Concat(stringCfg, " loc=\"X=", locationX, ",Y=", locationY, ",Z=", locationZ, + "\" rot=\"P=", rotationP, ",Y=", rotationY, ",R=", rotationR, "\""); + } + if (!string.IsNullOrEmpty(trackerCh) && tracker != null) + { + stringCfg = string.Concat(stringCfg, " tracker_id=", tracker.id, " tracker_ch=", trackerCh); + } + if (parent != null) + { + stringCfg = string.Concat(stringCfg, " parent=", parent.id); + } + stringCfg = string.Concat(stringCfg, "\n"); + return stringCfg; + } + + + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/SceneNodeView.cs b/Engine/Source/Programs/nDisplayLauncher/Config/SceneNodeView.cs new file mode 100644 index 000000000000..be6e76aec3ce --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/SceneNodeView.cs @@ -0,0 +1,64 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Windows.Input; + +namespace nDisplayLauncher.Config +{ + public class SceneNodeView + { + public List children { get; set; } + + public SceneNode node { get; set; } + + public bool isSelected { get; set; } + public bool isExpanded { get; set; } + + public SceneNodeView() + { + isSelected = false; + isExpanded = false; + children = new List(); + node = new SceneNode(); + } + public SceneNodeView(SceneNode item) + { + isSelected = false; + isExpanded = false; + children = new List(); + node = item; + } + + //Return child Scene node View if it equal argument scene node + public SceneNodeView FindNodeInChildren(SceneNodeView item) + { + SceneNodeView output = null; + if (this.node == item.node.parent) + { + output = this; + } + else + { + foreach (SceneNodeView child in this.children.ToList()) + { + if (child.node == item.node.parent) + { + output = child; + } + else + { + if (child.children.Count > 0) + { + output = child.FindNodeInChildren(item); + } + } + } + } + return output; + } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/Screen.cs b/Engine/Source/Programs/nDisplayLauncher/Config/Screen.cs new file mode 100644 index 000000000000..364fce723fdd --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/Screen.cs @@ -0,0 +1,173 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nDisplayLauncher.Config +{ + public class Screen : ConfigItem, IDataErrorInfo + { + + public string id { get; set; } + public string locationX { get; set; } + public string locationY { get; set; } + public string locationZ { get; set; } + public string rotationP { get; set; } + public string rotationY { get; set; } + public string rotationR { get; set; } + public string sizeX { get; set; } + public string sizeY { get; set; } + public SceneNode parentWall { get; set; } + + public Screen() + { + id = "ScreenId"; + locationX = "0"; + locationY = "0"; + locationZ = "0"; + rotationP = "0"; + rotationR = "0"; + rotationY = "0"; + sizeX = "0"; + sizeY = "0"; + parentWall = null; + } + + public Screen(string _id, string _locationX, string _locationY, string _locationZ, string _rotationP, string _rotationY, string _rotationR, string _sizeX, string _sizeY, SceneNode _parentWall) + { + id = _id; + locationX = _locationX; + locationY = _locationY; + locationZ = _locationZ; + rotationP = _rotationP; + rotationR = _rotationR; + rotationY = _rotationY; + sizeX = _sizeX; + sizeY = _sizeY; + parentWall = _parentWall; + } + + //Implementation IDataErrorInfo methods for validation + public string this[string columnName] + { + get + { + string error = String.Empty; + + if (columnName == "id" || columnName == validationName) + { + if (!ValidationRules.IsName(id)) + { + error = "Screen ID should contain only letters, numbers and _"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationX" || columnName == validationName) + { + if (!ValidationRules.IsFloat(locationX.ToString())) + { + error = "Location X should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationY" || columnName == validationName) + { + if (!ValidationRules.IsFloat(locationY.ToString())) + { + error = "Location Y should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationZ" || columnName == validationName) + { + if (!ValidationRules.IsFloat(locationZ.ToString())) + { + error = "Location Z should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationP" || columnName == validationName) + { + if (!ValidationRules.IsFloat(rotationP.ToString())) + { + error = "Pitch should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationY" || columnName == validationName) + { + if (!ValidationRules.IsFloat(rotationY.ToString())) + { + error = "Yaw should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationR" || columnName == validationName) + { + if (!ValidationRules.IsFloat(rotationR.ToString())) + { + error = "Roll should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "sizeX" || columnName == validationName) + { + if (!ValidationRules.IsFloat(sizeX) || (Convert.ToDouble(sizeX) < 0)) + { + error = "The X size parameter should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "sizeY" || columnName == validationName) + { + if (!ValidationRules.IsFloat(sizeY) || (Convert.ToDouble(sizeY) < 0)) + { + error = "The Y size parameter should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + + MainWindow.ConfigModifyIndicator(); + return error; + } + } + public string Error + { + get { throw new NotImplementedException(); } + } + + public override bool Validate() + { + bool isValid = ValidationRules.IsName(id) && ValidationRules.IsFloat(locationX.ToString()) && ValidationRules.IsFloat(locationY.ToString()) + && ValidationRules.IsFloat(locationZ.ToString()) && ValidationRules.IsFloat(rotationP.ToString()) && ValidationRules.IsFloat(rotationY.ToString()) + && ValidationRules.IsFloat(rotationR.ToString()) && (ValidationRules.IsFloat(sizeX) || (Convert.ToDouble(sizeX) > 0)) && (ValidationRules.IsFloat(sizeY) || (Convert.ToDouble(sizeY) > 0)); + if (!isValid) + { + AppLogger.Add("ERROR! Errors in Screen [" + id + "]"); + string a = this[validationName]; + + } + + return isValid; + } + + //Create String of screen parameters for config file + public override string CreateCfg() + { + string stringCfg = "[screen] "; + stringCfg = string.Concat(stringCfg, "id=", id, " loc=\"X=", locationX, ",Y=", locationY, ",Z=", locationZ, + "\" rot=\"P=", rotationP, ",Y=", rotationY, ",R=", rotationR, "\" size=\"X=", sizeX, ",Y=", sizeY, "\""); + if (parentWall != null) + { + stringCfg = string.Concat(stringCfg, " parent=", parentWall.id); + } + + stringCfg = string.Concat(stringCfg, "\n"); + return stringCfg; + } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/TrackerInput.cs b/Engine/Source/Programs/nDisplayLauncher/Config/TrackerInput.cs new file mode 100644 index 000000000000..63a4a068013a --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/TrackerInput.cs @@ -0,0 +1,164 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; + +namespace nDisplayLauncher.Config +{ + public class TrackerInput : BaseInput, IDataErrorInfo + { + public string locationX { get; set; } + public string locationY { get; set; } + public string locationZ { get; set; } + public string rotationP { get; set; } + public string rotationY { get; set; } + public string rotationR { get; set; } + public string front { get; set; } + public string right { get; set; } + public string up { get; set; } + + public TrackerInput() + { + id = "TrackerInputId"; + address = "TrackerInputName@127.0.0.1"; + type = InputDeviceType.tracker; + locationX = "0"; + locationY = "0"; + locationZ = "0"; + rotationP = "0"; + rotationY = "0"; + rotationR = "0"; + front = "X"; + right = "Y"; + up = "-Z"; + } + + public TrackerInput(string _id, string _address, string _locationX, string _locationY, string _locationZ, string _rotationP, string _rotationY, string _rotationR, string _front, string _right, string _up) + { + id = _id; + address = _address; + type = InputDeviceType.tracker; + locationX = _locationX; + locationY = _locationY; + locationZ = _locationZ; + rotationP = _rotationP; + rotationY = _rotationY; + rotationR = _rotationR; + front = _front; + right = _right; + up = _up; + } + + //Implementation IDataErrorInfo methods for validation + //ayamashev: check this 'new' + public new string this[string columnName] + { + get + { + string error = String.Empty; + if (columnName == "id" || columnName == validationName) + { + if (!ValidationRules.IsName(id)) + { + error = "Input ID should contain only letters, numbers and _"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "address" || columnName == validationName) + { + if (!ValidationRules.IsAddress(address)) + { + error = "Input Address in format InputName@127.0.0.1"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationX" || columnName == validationName) + { + if (!ValidationRules.IsFloat(locationX.ToString())) + { + error = "Location X should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationY" || columnName == validationName) + { + if (!ValidationRules.IsFloat(locationY.ToString())) + { + error = "Location Y should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "locationZ" || columnName == validationName) + { + if (!ValidationRules.IsFloat(locationZ.ToString())) + { + error = "Location Z should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationP" || columnName == validationName) + { + if (!ValidationRules.IsFloat(rotationP.ToString())) + { + error = "Pitch should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationY" || columnName == validationName) + { + if (!ValidationRules.IsFloat(rotationY.ToString())) + { + error = "Yaw should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "rotationR" || columnName == validationName) + { + if (!ValidationRules.IsFloat(rotationR.ToString())) + { + error = "Roll should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + + MainWindow.ConfigModifyIndicator(); + return error; + } + } + //ayamashev: check this 'new' + public new string Error + { + get { throw new NotImplementedException(); } + } + + public new bool Validate() + { + bool isValid = ValidationRules.IsName(id) && ValidationRules.IsAddress(address) && ValidationRules.IsFloat(locationX.ToString()) + && ValidationRules.IsFloat(locationY.ToString()) && ValidationRules.IsFloat(locationZ.ToString()) && ValidationRules.IsFloat(rotationP.ToString()) + && ValidationRules.IsFloat(rotationY.ToString()) && ValidationRules.IsFloat(rotationR.ToString()); + if (!isValid) + { + AppLogger.Add("ERROR! Errors in Input [" + id + "]"); + string a = this[validationName]; + + } + + return isValid; + } + + public override string CreateCfg() + { + string stringCfg = "[input] "; + stringCfg = string.Concat(stringCfg, "id=", id, " type=", type.ToString(), " addr=", address, + " loc=\"X=", locationX, ",Y=", locationY, ",Z=", locationZ, "\"", + " rot=\"P=", rotationP, ",Y=", rotationY, ",R=", rotationR, "\"", + " front=", front, " right=", right, " up=", up, "\n"); + + return stringCfg; + } + } +} \ No newline at end of file diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/VRConfig.cs b/Engine/Source/Programs/nDisplayLauncher/Config/VRConfig.cs new file mode 100644 index 000000000000..38dc62b57094 --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/VRConfig.cs @@ -0,0 +1,801 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.ComponentModel; +using System.Collections.Specialized; +using System.Collections; +using System.Text.RegularExpressions; + +namespace nDisplayLauncher.Config +{ + public class VRConfig : INotifyPropertyChanged, IDataErrorInfo + { + static readonly Dictionary header = new Dictionary + { + { "main", "#####################################################################\n# Info:\n# Note:\n#\n#####################################################################\n" }, + { "screen", "\n# List of screen configurations (transformations are in nDisplay space relative to the root node)\n"}, + { "camera", "\n# List of cameras \n"}, + { "viewport", "\n# List of viewport configurations \n"}, + { "cluster_node", "\n# List of cluster nodes \n"}, + { "stereo", "\n# eye_swap - false(L|R) <--> true(R|L) eye switch \n# eye_dist - interoccular distance (meters)\n"}, + {"scene_node", "\n# List of empty hierarchy nodes (transforms) \n" }, + { "debug", "\n# lag_simulation - enable/disable lag simulation \n# lag_max_time - maximum delay time for randome delay simulation \n"}, + { "general", "\n# 0 - no swap sync (V-sync off) \n# 1 - software swap synchronization over network \n# 2 - NVIDIA hardware swap synchronization (nv swaplock)\n"}, + { "input", "\n"} + }; + + private Dictionary _swapSyncPolicy = new Dictionary + { + { "0", "no swap sync (V-sync off)" }, + { "1", "software swap synchronization over network" }, + { "2", "NVIDIA hardware swap synchronization (nv swaplock)" } + }; + + + public Dictionary swapSyncPolicy + { + get { return _swapSyncPolicy; } + set { Set(ref _swapSyncPolicy, value, "swapSyncPolicy"); } + } + + private string _cameraLocationX; + public string cameraLocationX + { + get { return _cameraLocationX; } + set { Set(ref _cameraLocationX, value, "cameraLocationX"); } + } + + private string _cameraLocationY; + public string cameraLocationY + { + get { return _cameraLocationY; } + set { Set(ref _cameraLocationY, value, "cameraLocationY"); } + } + + private string _cameraLocationZ; + public string cameraLocationZ + { + get { return _cameraLocationZ; } + set { Set(ref _cameraLocationZ, value, "cameraLocationZ"); } + } + + //List of inputs + private List _inputs; + public List inputs + { + get { return _inputs; } + set { Set(ref _inputs, value, "inputs"); } + } + + //Selected input + private BaseInput _selectedInput; + public BaseInput selectedInput + { + get { return _selectedInput; } + set { Set(ref _selectedInput, value, "selectedInput"); } + } + + private TrackerInput _cameraTracker; + public TrackerInput cameraTracker + { + get { return _cameraTracker; } + set { Set(ref _cameraTracker, value, "cameraTracker"); } + } + + private string _cameraTrackerCh; + public string cameraTrackerCh + { + get { return _cameraTrackerCh; } + set { Set(ref _cameraTrackerCh, value, "cameraTrackerCh"); } + } + + //List of cluster nodes + private List _clusterNodes; + public List clusterNodes + { + get { return _clusterNodes; } + set { Set(ref _clusterNodes, value, "clusterNodes"); } + } + //Selected cluster node + private ClusterNode _selectedNode; + public ClusterNode selectedNode + { + get { return _selectedNode; } + set { Set(ref _selectedNode, value, "selectedNode"); } + } + + + //List of screens + private List _screens; + public List screens + { + get { return _screens; } + set { Set(ref _screens, value, "screens"); } + + } + //Selected screen + private Screen _selectedScreen; + public Screen selectedScreen + { + get { return _selectedScreen; } + set { Set(ref _selectedScreen, value, "selectedScreen"); } + } + + //List of viewports + private List _viewports; + public List viewports + { + get { return _viewports; } + set { Set(ref _viewports, value, "viewports"); } + } + //Selected Viewport + private Viewport _selectedViewport; + public Viewport selectedViewport + { + get { return _selectedViewport; } + set { Set(ref _selectedViewport, value, "selectedViewport"); } + } + + //List of scene nodes + private List _sceneNodes; + public List sceneNodes + { + get { return _sceneNodes; } + set { Set(ref _sceneNodes, value, "sceneNodes"); } + } + + //List of scene nodes view + private List _sceneNodesView; + public List sceneNodesView + { + get { return _sceneNodesView; } + set { Set(ref _sceneNodesView, value, "sceneNodesView"); } + } + + //Selected SceneNodeView + private SceneNodeView _selectedSceneNodeView; + public SceneNodeView selectedSceneNodeView + { + get { return _selectedSceneNodeView; } + set { Set(ref _selectedSceneNodeView, value, "selectedSceneNodeView"); } + } + + + //Master node settings + private string _portCs; + public string portCs + { + get { return _portCs; } + set { Set(ref _portCs, value, "portCs"); } + } + + private string _portSs; + public string portSs + { + get { return _portSs; } + set { Set(ref _portSs, value, "portSs"); } + } + + //Stereo settings + private string _eyeDist; + public string eyeDist + { + get { return _eyeDist; } + set { Set(ref _eyeDist, value, "eyeDist"); } + } + + private bool _eyeSwap; + public bool eyeSwap + { + get { return _eyeSwap; } + set { Set(ref _eyeSwap, value, "eyeSwap"); } + } + + //Debug settings + private bool _lagSimulation; + public bool lagSimulation + { + get { return _lagSimulation; } + set { Set(ref _lagSimulation, value, "lagSimulation"); } + } + + private string _lagMaxTime; + public string lagMaxTime + { + get { return _lagMaxTime; } + set { Set(ref _lagMaxTime, value, "lagMaxTime"); } + } + + private bool _drawStats; + public bool drawStats + { + get { return _drawStats; } + set { Set(ref _drawStats, value, "drawStats"); } + } + + private KeyValuePair _selectedSwapSync; + public KeyValuePair selectedSwapSync + { + get { return _selectedSwapSync; } + set { Set(ref _selectedSwapSync, value, "selectedSwapSync"); } + } + + //Config name(FileName witout extention) + public string name = "New Config"; + + //String for validation all properties + string validationName = "Object validation"; + + public VRConfig() + { + clusterNodes = new List(); + viewports = new List(); + sceneNodes = new List(); + sceneNodes = new List(); + sceneNodesView = ConvertSceneNodeList(sceneNodes); + screens = new List(); + inputs = new List(); + selectedSceneNodeView = new SceneNodeView(new SceneNode + { + id = string.Empty, + locationX = string.Empty, + locationY = string.Empty, + locationZ = string.Empty, + rotationP = string.Empty, + rotationY = string.Empty, + rotationR = string.Empty, + tracker = new TrackerInput(), + trackerCh = string.Empty, + parent = null + }); + + + cameraLocationX = "0"; + cameraLocationY = "0"; + cameraLocationZ = "0"; + cameraTracker = null; + cameraTrackerCh = "0"; + + //Stereo settings + eyeDist = "0.064"; + eyeSwap = false; + + //Debug settings + lagSimulation = false; + lagMaxTime = "0"; + drawStats = false; + + //Master node settings + portCs = "00001"; + portSs = "00000"; + + //General settings + string defaultSwapSync = "1"; + if (swapSyncPolicy.ContainsKey(defaultSwapSync)) + { + selectedSwapSync = swapSyncPolicy.FirstOrDefault(x => x.Key == defaultSwapSync); + } + + + } + //Implementation IDataErrorInfo methods + public string this[string columnName] + { + get + { + string error = String.Empty; + if (columnName == "cameraLocationX" || columnName == validationName) + { + if (!ValidationRules.IsFloat(cameraLocationX.ToString())) + { + error = "Camera location X should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "cameraLocationY" || columnName == validationName) + { + if (!ValidationRules.IsFloat(cameraLocationY.ToString())) + { + error = "Camera location Y should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "cameraLocationZ" || columnName == validationName) + { + if (!ValidationRules.IsFloat(cameraLocationZ.ToString())) + { + error = "Camera location Z should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "cameraTrackerCh" || columnName == validationName) + { + if (!ValidationRules.IsIntNullable(cameraTrackerCh)) + { + error = "Camera tracker channel should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "eyeDist" || columnName == validationName) + { + if (!ValidationRules.IsFloat(eyeDist.ToString())) + { + error = "Eye distance should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "lagMaxTime" || columnName == validationName) + { + if (!ValidationRules.IsFloat(lagMaxTime.ToString())) + { + error = "Maximum delay time should be a floating point number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "portCs" || columnName == validationName) + { + if (!(ValidationRules.IsInt(portCs) || Convert.ToInt32(portCs) > 0)) + { + error = "Client port should be a positive number"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "portSs" || columnName == validationName) + { + if (!(ValidationRules.IsInt(portSs) || Convert.ToInt32(portSs) > 0)) + { + error = "Server port should be a positive number"; + AppLogger.Add("ERROR! " + error); + } + } + + MainWindow.ConfigModifyIndicator(); + return error; + } + } + public string Error + { + get { throw new NotImplementedException(); } + } + + public bool Validate() + { + bool isValid = ValidationRules.IsFloat(cameraLocationX.ToString()) && ValidationRules.IsFloat(cameraLocationY.ToString()) + && ValidationRules.IsFloat(cameraLocationZ.ToString()) && ValidationRules.IsIntNullable(cameraTrackerCh) && ValidationRules.IsFloat(eyeDist) + && ValidationRules.IsFloat(lagMaxTime) && (ValidationRules.IsInt(portCs) || (Convert.ToDouble(portCs) > 0)) && (ValidationRules.IsInt(portSs) || (Convert.ToDouble(portSs) > 0)); + if (!isValid) + { + string a = this[validationName]; + } + //Validate items in lists + isValid = isValid && ValidateList(ref _screens); + isValid = isValid && ValidateList(ref _clusterNodes); + isValid = isValid && ValidateList(ref _viewports); + isValid = isValid && ValidateList(ref _sceneNodes); + isValid = isValid && ValidateList(ref _inputs); + + //Check id uniqueness for listitems + if (screens.GroupBy(n => n.id).Any(c => c.Count() > 1)) + { + isValid = false; + AppLogger.Add("ERROR! All Screen Id's should be unique! Rename duplicate Id's"); + } + if (clusterNodes.GroupBy(n => n.id).Any(c => c.Count() > 1)) + { + isValid = false; + AppLogger.Add("ERROR! All Cluster node Id's should be unique! Rename duplicate Id's"); + } + if (viewports.GroupBy(n => n.id).Any(c => c.Count() > 1)) + { + isValid = false; + AppLogger.Add("ERROR! All Viewport Id's should be unique! Rename duplicate Id's"); + } + if (sceneNodes.GroupBy(n => n.id).Any(c => c.Count() > 1)) + { + isValid = false; + AppLogger.Add("ERROR! All Scene node Id's should be unique! Rename duplicate Id's"); + } + if (inputs.GroupBy(n => n.id).Any(c => c.Count() > 1)) + { + isValid = false; + AppLogger.Add("ERROR! All Input Id's should be unique! Rename duplicate Id's"); + } + + //check master node + if (!clusterNodes.Exists(x => x.isMaster)) + { + isValid = false; + AppLogger.Add("ERROR! Master Cluster Node is not selected! Check Master Node"); + } + + return isValid; + } + + bool ValidateList(ref List list) where T : ConfigItem + { + bool isValid = true; + foreach (T item in list) + { + isValid = isValid && item.Validate(); + } + return isValid; + } + + + + + //Implementation of INotifyPropertyChanged method for TwoWay binding + public event PropertyChangedEventHandler PropertyChanged; + + protected void OnNotifyPropertyChanged(string propertyName) + { + if (PropertyChanged != null) + { + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + } + + //Set property with OnNotifyPropertyChanged call + protected void Set(ref T field, T newValue, string propertyName) + { + field = newValue; + OnNotifyPropertyChanged(propertyName); + } + + public string CreateConfig() + { + string configFile = string.Empty; + configFile = string.Concat(string.Empty, header["main"], header["cluster_node"], CreateListCfg(ref _clusterNodes), + header["screen"], CreateListCfg(ref _screens), + header["viewport"], CreateListCfg(ref _viewports), + header["camera"], "[camera] id=camera_static loc=\"X=", cameraLocationX, ",Y=", cameraLocationY, ",Z=", cameraLocationZ, "\"\n"); + if (cameraTracker != null && !string.IsNullOrEmpty(cameraTrackerCh)) + { + configFile = string.Concat(configFile, "[camera] id=camera_dynamic loc=\"X=", cameraLocationX, ",Y=", cameraLocationY, ",Z=", cameraLocationZ, "\" tracker_id=", cameraTracker.id, " tracker_ch=", cameraTrackerCh, "\n"); + } + configFile = string.Concat(configFile, header["scene_node"], CreateListCfg(ref _sceneNodes), + header["input"], CreateListCfg(ref _inputs), + header["stereo"], "[stereo] eye_swap=", eyeSwap.ToString(), " eye_dist=", eyeDist, "\n", + header["debug"], "[debug] lag_simulation=", lagSimulation.ToString(), " lag_max_time=", lagMaxTime, " draw_stats=", drawStats.ToString(), "\n", + header["general"], "[general] swap_sync_policy=", selectedSwapSync.Key, "\n" + ); + return configFile; + } + + //Create part of config for List items (IConfigItem) + string CreateListCfg(ref List list) where T : ConfigItem + { + string stringCfg = string.Empty; + foreach (T item in list) + { + stringCfg = string.Concat(stringCfg, item.CreateCfg()); + } + return stringCfg; + } + + + //Inputs Parser + public void InputsParse(string line) + { + string id = GetRegEx("id").Match(line).Value; + string address = GetRegAddr("addr").Match(line).Value; + string _type = GetRegEx("type").Match(line).Value; + InputDeviceType type = (InputDeviceType)Enum.Parse(typeof(InputDeviceType), _type, true); + if (type == InputDeviceType.tracker) + { + string loc = GetRegComplex("loc").Match(line).Value; + string locX = GetRegProp("X").Match(loc).Value; + string locY = GetRegProp("Y").Match(loc).Value; + string locZ = GetRegProp("Z").Match(loc).Value; + string rot = GetRegComplex("rot").Match(line).Value; + string rotP = GetRegProp("P").Match(rot).Value; + string rotY = GetRegProp("Y").Match(rot).Value; + string rotR = GetRegProp("R").Match(rot).Value; + string front = GetRegEx("front").Match(line).Value; + string right = GetRegEx("right").Match(line).Value; + string up = GetRegEx("up").Match(line).Value; + inputs.Add(new TrackerInput(id, address, locX, locY, locZ, rotP, rotY, rotR, front, right, up)); + } + else + { + inputs.Add(new BaseInput(id, type, address)); + } + } + + //Cluster Node Parser + public void ClusterNodeParse(string line) + { + string id = GetRegEx("id").Match(line).Value; + string address = GetRegIp("addr").Match(line).Value; + string screen = GetRegEx("screen").Match(line).Value; + string viewport = GetRegEx("viewport").Match(line).Value; + string camera = GetRegEx("camera").Match(line).Value.ToLower(); + string master = GetRegEx("master").Match(line).Value.ToLower(); + + //window settings + string windowed = GetRegEx("windowed").Match(line).Value.ToLower(); + string winX = string.Empty; + string winY = string.Empty; + string resX = string.Empty; + string resY = string.Empty; + bool isWindowed = false; + if (windowed == "true") + { + isWindowed = true; + } + winX = GetRegEx("winX").Match(line).Value; + winY = GetRegEx("winY").Match(line).Value; + resX = GetRegEx("resX").Match(line).Value; + resY = GetRegEx("resY").Match(line).Value; + + //Master node settings + bool isMaster = false; + if (master == "true") + { + isMaster = true; + string port_cs = GetRegEx("port_cs").Match(line).Value; + portCs = port_cs; + string port_ss = GetRegEx("port_ss").Match(line).Value; + portSs = port_ss; + } + Screen currentScreen = screens.Find(x => x.id == screen); + Viewport currentViewport = viewports.Find(x => x.id == viewport); + clusterNodes.Add(new ClusterNode(id, address, currentScreen, currentViewport, camera, isMaster, isWindowed, winX, winY, resX, resY)); + } + + //Scene Node Parser + public void SceneNodeParse(string line) + { + string id = GetRegEx("id").Match(line).Value; + string loc = GetRegComplex("loc").Match(line).Value; + string locX = GetRegProp("X").Match(loc).Value; + string locY = GetRegProp("Y").Match(loc).Value; + string locZ = GetRegProp("Z").Match(loc).Value; + string rot = GetRegComplex("rot").Match(line).Value; + string rotP = GetRegProp("P").Match(rot).Value; + string rotY = GetRegProp("Y").Match(rot).Value; + string rotR = GetRegProp("R").Match(rot).Value; + + string _trackerId = GetRegEx("tracker_id").Match(line).Value; + TrackerInput tracker = (TrackerInput)inputs.Find(x => x.id == _trackerId); + string _trackerCh = GetRegEx("tracker_ch").Match(line).Value; + + string _parent = GetRegEx("parent").Match(line).Value; + SceneNode parent = sceneNodes.Find(x => x.id == _parent); + + sceneNodes.Add(new SceneNode(id, locX, locY, locZ, rotP, rotY, rotR, tracker, _trackerCh, parent)); + } + + //Screen Parser + public void ScreenParse(string line) + { + string id = GetRegEx("id").Match(line).Value; + string loc = GetRegComplex("loc").Match(line).Value; + string locX = GetRegProp("X").Match(loc).Value; + string locY = GetRegProp("Y").Match(loc).Value; + string locZ = GetRegProp("Z").Match(loc).Value; + string rot = GetRegComplex("rot").Match(line).Value; + string rotP = GetRegProp("P").Match(rot).Value; + string rotY = GetRegProp("Y").Match(rot).Value; + string rotR = GetRegProp("R").Match(rot).Value; + string size = GetRegComplex("size").Match(line).Value; + string sizeX = GetRegProp("X").Match(size).Value; + string sizeY = GetRegProp("Y").Match(size).Value; + string _parent = GetRegEx("parent").Match(line).Value; + SceneNode parent = sceneNodes.Find(x => x.id == _parent); + screens.Add(new Screen(id, locX, locY, locZ, rotP, rotY, rotR, sizeX, sizeY, parent)); + } + + //Viewport Parser + public void ViewportParse(string line) + { + string id = GetRegEx("id").Match(line).Value; + string x = GetRegEx("x").Match(line).Value; + string y = GetRegEx("y").Match(line).Value; + string width = GetRegEx("width").Match(line).Value; + string height = GetRegEx("height").Match(line).Value; + string _flip_h = GetRegEx("flip_h").Match(line).Value.ToLower(); + string _flip_v = GetRegEx("flip_v").Match(line).Value.ToLower(); + //string _isWindowed = GetRegEx("windowed").Match(line).Value.ToLower(); + bool flip_h = false; + bool flip_v = false; + //bool isWindowed = false; + if (_flip_h == "true") + { + flip_h = true; + } + if (_flip_v == "true") + { + flip_v = true; + } + //if (_isWindowed == "true") + //{ + // isWindowed = true; + //} + viewports.Add(new Viewport(id, x, y, width, height, flip_h, flip_v)); + } + + //Camera Parser + public void CameraParse(string line) + { + string loc = GetRegComplex("loc").Match(line).Value; + string locX = GetRegProp("X").Match(loc).Value; + string locY = GetRegProp("Y").Match(loc).Value; + string locZ = GetRegProp("Z").Match(loc).Value; + string cameraTrackerId = GetRegEx("tracker_id").Match(line).Value; + string trackerCh = GetRegEx("tracker_ch").Match(line).Value; + cameraLocationX = locX; + cameraLocationY = locY; + cameraLocationZ = locZ; + cameraTracker = (TrackerInput)inputs.Find(x => x.id == cameraTrackerId); + cameraTrackerCh = trackerCh; + } + + //General Parser + public void GeneralParse(string line) + { + string swapSync = GetRegEx("swap_sync_policy").Match(line).Value; + selectedSwapSync = swapSyncPolicy.FirstOrDefault(x => x.Key == swapSync); + } + + //Stereo Parser + public void StereoParse(string line) + { + string eye_swap = GetRegEx("eye_swap").Match(line).Value; + string eye_dist = GetRegEx("eye_dist").Match(line).Value; + eyeSwap = Convert.ToBoolean(eye_swap); + eyeDist = eye_dist; + } + + //Debug Parser + public void DebugParse(string line) + { + string lag_simulation = GetRegEx("lag_simulation").Match(line).Value; + string lag_max_time = GetRegEx("lag_max_time").Match(line).Value; + string draw_stats = GetRegEx("draw_stats").Match(line).Value; + lagSimulation = Convert.ToBoolean(lag_simulation); + lagMaxTime = lag_max_time; + drawStats = Convert.ToBoolean(draw_stats); + } + + //Gets double value of parameter from source string + //private string GetDoubleFromString(string param, string source) + //{ + // string value = GetRegProp(param).Match(source).Value; + // return ParseToDoubleWithDefault(value); + //} + + ////Converting string value to double and sets 0 if error + //private string ParseToDoubleWithDefault(string value) + //{ + // double parsedValue = 0; + // try + // { + // parsedValue = Convert.ToDouble(value); + // } + // catch (Exception exception) + // { + // //add log + // } + // return parsedValue; + //} + + //Create Regex for string-num values + Regex GetRegEx(string key) + { + Regex reg = new Regex("(?<=(?i)(" + key + ")=)(-??\\w*\\.??\\w*)(?=\\s|$)"); + return reg; + } + ////Create Regex for complex values(location, rotation, size) + Regex GetRegComplex(string key) + { + Regex reg = new Regex("(?<=(?i)(" + key + ")=\")(.*)(?=\"\\s|$)"); + return reg; + } + + //Create Regex for IP address values + Regex GetRegIp(string key) + { + Regex reg = new Regex("(?<=(?i)(" + key + ")=)((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(?=\\s|$)"); + return reg; + } + + //Create Regex input address values + Regex GetRegAddr(string key) + { + Regex reg = new Regex("(?<=(?i)(" + key + ")=)(\\w*)@((([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]).){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5]))(?=\\s|$)"); + return reg; + } + + //Create Regex for location, rotation and size properties + Regex GetRegProp(string key) + { + Regex reg = new Regex("(?<=(?i)(" + key + ")=)(-??\\w*.??\\w*)(?=,|\"|$)"); + return reg; + } + + //Select first items in lists if existed + public void SelectFirstItems() + { + selectedInput = inputs.FirstOrDefault(); + selectedNode = clusterNodes.FirstOrDefault(); + selectedSceneNodeView = sceneNodesView.FirstOrDefault(); + try + { + selectedSceneNodeView.isSelected = true; + } + catch (NullReferenceException) + { + + } + selectedScreen = screens.FirstOrDefault(); + selectedViewport = viewports.FirstOrDefault(); + + } + + //Convert list of scene nodes to hierarhical list of scene node views + public List ConvertSceneNodeList(List inputList) + { + List outputList = new List(); + foreach (SceneNode item in inputList) + { + outputList.Add(new SceneNodeView(item)); + } + foreach (SceneNodeView item in outputList) + { + item.children = outputList.FindAll(x => (x.node.parent != null) && (x.node.parent.id == item.node.id)); + } + + return outputList.FindAll(x => x.node.parent == null); + } + + public void DeleteSceneNode(SceneNodeView item) + { + if (item.children != null) + { + foreach (SceneNodeView child in item.children.ToList()) + { + DeleteSceneNode(child); + + } + } + if (item.node.parent == null) + { + sceneNodesView.Remove(item); + + } + else + { + SceneNodeView parentNode = FindParentNode(item); + if (parentNode != null) + { + parentNode.children.Remove(item); + } + } + AppLogger.Add("Scene node " + item.node.id + " deleted"); + sceneNodes.Remove(item.node); + } + + public SceneNodeView FindParentNode(SceneNodeView item) + { + SceneNodeView parentNode = sceneNodesView.Find(x => x.node == item.node.parent); + if (parentNode == null) + { + foreach (SceneNodeView node in sceneNodesView) + { + if (node.FindNodeInChildren(item) != null) + { + parentNode = node.FindNodeInChildren(item); + } + } + } + return parentNode; + } + } +} \ No newline at end of file diff --git a/Engine/Source/Programs/nDisplayLauncher/Config/Viewport.cs b/Engine/Source/Programs/nDisplayLauncher/Config/Viewport.cs new file mode 100644 index 000000000000..3110ac4ffbd7 --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/Config/Viewport.cs @@ -0,0 +1,158 @@ +// Copyright 1998-2018 Epic Games, Inc. All Rights Reserved. + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace nDisplayLauncher.Config +{ + public class Viewport : ConfigItem, IDataErrorInfo + { + public string id { get; set; } + public string x { get; set; } + public string y { get; set; } + public string width { get; set; } + public string height { get; set; } + public bool horizontalFlip { get; set; } + public bool verticalFlip { get; set; } + + public Viewport() + { + id = "ViewportId"; + x = "0"; + y = "0"; + width = "0"; + height = "0"; + horizontalFlip = false; + verticalFlip = false; + } + + public Viewport(string _id, string _x, string _y, string _width, string _height, bool _horizontalFlip, bool _verticalFlip) + { + id = _id; + x = _x; + y = _y; + width = _width; + height = _height; + horizontalFlip = _horizontalFlip; + verticalFlip = _verticalFlip; + } + + //Implementation IDataErrorInfo methods for validation + public string this[string columnName] + { + get + { + string error = String.Empty; + if (columnName == "id" || columnName == validationName) + { + if (!ValidationRules.IsName(id)) + { + error = "Viewport ID should contain only letters, numbers and _"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "x" || columnName == validationName) + { + if (!ValidationRules.IsInt(x.ToString())) + { + error = "x should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "y" || columnName == validationName) + { + if (!ValidationRules.IsInt(y.ToString())) + { + error = "y should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + if (columnName == "width" || columnName == validationName) + { + if (!ValidationRules.IsInt(width.ToString()) || Convert.ToInt32(width) < 0) + { + error = "Width should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + + if (columnName == "height" || columnName == validationName) + { + if (!ValidationRules.IsInt(height.ToString()) || Convert.ToInt32(height) < 0) + { + error = "Height should be an integer"; + AppLogger.Add("ERROR! " + error); + } + } + //switch (columnName) + //{ + // case "id": + // if (!ValidationRules.IsName(id)) + // { + // error = "Viewport ID should contain only letters, numbers and _"; + // } + // break; + // case "x": + // if (!ValidationRules.IsInt(x.ToString())) + // { + // error = "x should be an integer"; + // } + // break; + // case "y": + // if (!ValidationRules.IsInt(y.ToString())) + // { + // error = "y should be an integer"; + // } + // break; + // case "width": + // if (!ValidationRules.IsInt(width.ToString())) + // { + // error = "Width should be an integer"; + // } + // break; + // case "height": + // if (!ValidationRules.IsInt(height.ToString())) + // { + // error = "Height should be an integer"; + // } + // break; + //} + //if (!string.IsNullOrEmpty(error)) + //{ + // AppLogger.Add("ERROR! " + error); + //} + return error; + } + } + public string Error + { + get { throw new NotImplementedException(); } + } + + public override bool Validate() + { + bool isValid = ValidationRules.IsName(id) && ValidationRules.IsInt(x.ToString()) && ValidationRules.IsInt(y.ToString()) + && (ValidationRules.IsInt(width.ToString()) || Convert.ToInt32(width) > 0) && (ValidationRules.IsInt(height.ToString()) || Convert.ToInt32(height) > 0); + if (!isValid) + { + AppLogger.Add("ERROR! Errors in Viewport [" + id + "]"); + string a = this[validationName]; + + } + + return isValid; + } + + public override string CreateCfg() + { + string stringCfg = "[viewport] "; + stringCfg = string.Concat(stringCfg, "id=", id, " x=", x, " y=", y, " width=", width, " height=", height, " flip_h=", horizontalFlip.ToString(), " flip_v=", verticalFlip, "\n"); + + return stringCfg; + } + } +} diff --git a/Engine/Source/Programs/nDisplayLauncher/MainWindow.xaml b/Engine/Source/Programs/nDisplayLauncher/MainWindow.xaml new file mode 100644 index 000000000000..4a3d8d27725d --- /dev/null +++ b/Engine/Source/Programs/nDisplayLauncher/MainWindow.xaml @@ -0,0 +1,603 @@ + + + + + + + + + + ^[a-zA-Z][\w\.-]*[a-zA-Z0-9]@ + [a-zA-Z0-9][\w\.-]*[a-zA-Z0-9]\.[a-zA-Z][a-zA-Z\.] + *[a-zA-Z]$ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +