diff --git a/Config/DefaultEditor.ini b/Config/DefaultEditor.ini new file mode 100644 index 0000000000000000000000000000000000000000..d3f5a12faa99758192ecc4ed3fc22c9249232e86 --- /dev/null +++ b/Config/DefaultEditor.ini @@ -0,0 +1 @@ + diff --git a/Config/DefaultEngine.ini b/Config/DefaultEngine.ini new file mode 100644 index 0000000000000000000000000000000000000000..7aa3194daf574a0333c5fa1fa2a5619f84ba7af3 --- /dev/null +++ b/Config/DefaultEngine.ini @@ -0,0 +1,306 @@ +[/Script/EngineSettings.GameMapsSettings] +GameDefaultMap=/CyberArchWarehouse/SpatialAnchorsSample/Maps/SA_Showcase.SA_Showcase +EditorStartupMap=/CyberArchWarehouse/xrPerson/Maps/EmptyStart.EmptyStart +GlobalDefaultGameMode=/CyberArchWarehouse/xrPerson/Blueprints/BP_FirstPersonGameMode.BP_FirstPersonGameMode_C + +[/Script/HardwareTargeting.HardwareTargetingSettings] +TargetedHardwareClass=Desktop +AppliedTargetedHardwareClass=Desktop +DefaultGraphicsPerformance=Maximum +AppliedDefaultGraphicsPerformance=Maximum + +[/Script/WindowsTargetPlatform.WindowsTargetSettings] +Compiler=Default +-TargetedRHIs=PCD3D_SM5 ++TargetedRHIs=PCD3D_SM5 ++TargetedRHIs=PCD3D_SM6 +DefaultGraphicsRHI=DefaultGraphicsRHI_DX12 +AudioSampleRate=48000 +AudioCallbackBufferFrameSize=1024 +AudioNumBuffersToEnqueue=1 +AudioMaxChannels=0 +AudioNumSourceWorkers=4 +SpatializationPlugin= +SourceDataOverridePlugin= +ReverbPlugin= +OcclusionPlugin= +CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) +CacheSizeKB=65536 +MaxChunkSizeOverrideKB=0 +bResampleForDevice=False +MaxSampleRate=48000.000000 +HighSampleRate=32000.000000 +MedSampleRate=24000.000000 +LowSampleRate=12000.000000 +MinSampleRate=8000.000000 +CompressionQualityModifier=1.000000 +AutoStreamingThreshold=0.000000 +SoundCueCookQualityIndex=-1 + +[/Script/Engine.RendererSettings] +r.Mobile.DisableVertexFog=False +r.Shadow.CSM.MaxMobileCascades=2 +r.Mobile.AntiAliasing=3 +r.Mobile.FloatPrecisionMode=0 +r.Mobile.AllowDitheredLODTransition=False +r.Mobile.VirtualTextures=False +r.Mobile.ReflectionCaptureCompression=False +r.DiscardUnusedQuality=False +r.Shaders.CompressionFormat=2 +r.AllowOcclusionQueries=True +r.MinScreenRadiusForLights=0.030000 +r.MinScreenRadiusForDepthPrepass=0.030000 +r.MinScreenRadiusForCSMDepth=0.010000 +r.PrecomputedVisibilityWarning=False +r.TextureStreaming=True +Compat.UseDXT5NormalMaps=False +r.VirtualTextures=False +r.VT.EnableAutoImport=True +r.VirtualTexturedLightmaps=False +r.VT.AnisotropicFiltering=False +r.VT.TileSize=128 +r.VT.TileBorderSize=4 +r.vt.FeedbackFactor=16 +WorkingColorSpaceChoice=sRGB +RedChromaticityCoordinate=(X=0.640000,Y=0.330000) +GreenChromaticityCoordinate=(X=0.300000,Y=0.600000) +BlueChromaticityCoordinate=(X=0.150000,Y=0.060000) +WhiteChromaticityCoordinate=(X=0.312700,Y=0.329000) +r.ClearCoatNormal=False +r.DynamicGlobalIlluminationMethod=1 +r.ReflectionMethod=1 +r.ReflectionCaptureResolution=128 +r.ReflectionEnvironmentLightmapMixBasedOnRoughness=True +r.Lumen.HardwareRayTracing=False +r.Lumen.HardwareRayTracing.LightingMode=0 +r.Lumen.TraceMeshSDFs=1 +r.Shadow.Virtual.Enable=1 +r.RayTracing=False +r.RayTracing.Shadows=False +r.RayTracing.Skylight=False +r.RayTracing.UseTextureLod=False +r.PathTracing=True +r.GenerateMeshDistanceFields=True +r.DistanceFields.DefaultVoxelDensity=0.200000 +r.AllowStaticLighting=True +r.NormalMapsForStaticLighting=False +r.ForwardShading=False +r.VertexFoggingForOpaque=True +r.SeparateTranslucency=True +r.TranslucentSortPolicy=0 +TranslucentSortAxis=(X=0.000000,Y=-1.000000,Z=0.000000) +vr.VRS.HMDFixedFoveationLevel=0 +vr.VRS.HMDFixedFoveationDynamic=False +r.CustomDepth=1 +r.CustomDepthTemporalAAJitter=True +r.PostProcessing.PropagateAlpha=2 +r.DefaultFeature.Bloom=True +r.DefaultFeature.AmbientOcclusion=True +r.DefaultFeature.AmbientOcclusionStaticFraction=True +r.DefaultFeature.AutoExposure=False +r.DefaultFeature.AutoExposure.Method=0 +r.DefaultFeature.AutoExposure.Bias=1.000000 +r.DefaultFeature.AutoExposure.ExtendDefaultLuminanceRange=False +r.DefaultFeature.MotionBlur=True +r.DefaultFeature.LensFlare=False +r.TemporalAA.Upsampling=True +r.AntiAliasingMethod=4 +r.MSAACount=4 +r.DefaultFeature.LightUnits=1 +r.DefaultBackBufferPixelFormat=4 +r.Shadow.UnbuiltPreviewInGame=True +r.StencilForLODDither=False +r.EarlyZPass=3 +r.EarlyZPassOnlyMaterialMasking=False +r.Shadow.CSMCaching=False +r.DBuffer=True +r.ClearSceneMethod=1 +r.VelocityOutputPass=0 +r.Velocity.EnableVertexDeformation=2 +r.SelectiveBasePassOutputs=False +bDefaultParticleCutouts=False +fx.GPUSimulationTextureSizeX=1024 +fx.GPUSimulationTextureSizeY=1024 +r.AllowGlobalClipPlane=False +r.GBufferFormat=1 +r.MorphTarget.Mode=True +r.GPUCrashDebugging=False +vr.InstancedStereo=False +r.MobileHDR=False +vr.MobileMultiView=True +r.Mobile.UseHWsRGBEncoding=True +vr.RoundRobinOcclusion=False +vr.ODSCapture=False +r.MeshStreaming=False +r.WireframeCullThreshold=5.000000 +r.SupportStationarySkylight=True +r.SupportLowQualityLightmaps=True +r.SupportPointLightWholeSceneShadows=True +r.SupportSkyAtmosphere=True +r.SupportSkyAtmosphereAffectsHeightFog=True +r.SupportCloudShadowOnForwardLitTranslucent=False +r.Material.RoughDiffuse=False +r.Material.EnergyConservation=False +r.SkinCache.CompileShaders=True +r.SkinCache.DefaultBehavior=0 +r.SkinCache.SceneMemoryLimitInMB=128.000000 +r.Mobile.EnableStaticAndCSMShadowReceivers=True +r.Mobile.EnableMovableLightCSMShaderCulling=True +r.Mobile.EnableNoPrecomputedLightingCSMShader=False +r.Mobile.AllowDistanceFieldShadows=True +r.Mobile.AllowMovableDirectionalLights=True +r.MobileNumDynamicPointLights=4 +r.Mobile.EnableMovableSpotlights=False +r.Mobile.EnableMovableSpotlightsShadow=False +r.GPUSkin.Support16BitBoneIndex=True +r.GPUSkin.Limit2BoneInfluences=False +r.SupportDepthOnlyIndexBuffers=True +r.SupportReversedIndexBuffers=True +r.Mobile.AmbientOcclusion=False +r.GPUSkin.UnlimitedBoneInfluences=True +r.GPUSkin.UnlimitedBoneInfluencesThreshold=8 +MaxSkinBones=(Default=65536,PerPlatform=(("Mobile", 256))) +r.Mobile.PlanarReflectionMode=0 +r.Mobile.SupportsGen4TAA=True +bStreamSkeletalMeshLODs=(Default=False,PerPlatform=()) +bDiscardSkeletalMeshOptionalLODs=(Default=False,PerPlatform=()) +VisualizeCalibrationColorMaterialPath=/Engine/EngineMaterials/PPM_DefaultCalibrationColor.PPM_DefaultCalibrationColor +VisualizeCalibrationCustomMaterialPath=None +VisualizeCalibrationGrayscaleMaterialPath=/Engine/EngineMaterials/PPM_DefaultCalibrationGrayscale.PPM_DefaultCalibrationGrayscale +SkeletalMesh.UseExperimentalChunking=1 + +[/Script/WorldPartitionEditor.WorldPartitionEditorSettings] +CommandletClass=Class'/Script/UnrealEd.WorldPartitionConvertCommandlet' + +[/Script/Engine.Engine] ++ActiveGameNameRedirects=(OldGameName="TP_BlankBP",NewGameName="/Script/CyberProject") ++ActiveGameNameRedirects=(OldGameName="/Script/TP_BlankBP",NewGameName="/Script/CyberProject") + +[/Script/AndroidFileServerEditor.AndroidFileServerRuntimeSettings] +bEnablePlugin=True +bAllowNetworkConnection=True +SecurityToken=10CAFFFC4A971E6E677468A095347CF2 +bIncludeInShipping=False +bAllowExternalStartInShipping=False +bCompileAFSProject=False +bUseCompression=False +bLogFiles=False +bReportStats=False +ConnectionType=USBOnly +bUseManualIPAddress=False +ManualIPAddress= + +[/Script/OculusXRHMD.OculusXRHMDRuntimeSettings] +XrApi=OVRPluginOpenXR +HandTrackingSupport=ControllersAndHands +bInsightPassthroughEnabled=True +bAnchorSupportEnabled=True +bSupportExperimentalFeatures=True + +[/Script/AndroidRuntimeSettings.AndroidRuntimeSettings] +PackageName=com.FalconViz.ArHBIM +StoreVersion=1 +StoreVersionOffsetArmV7=0 +StoreVersionOffsetArm64=0 +StoreVersionOffsetX8664=0 +ApplicationDisplayName= +VersionDisplayName=1.0 +MinSDKVersion=29 +TargetSDKVersion=29 +InstallLocation=InternalOnly +bEnableGradle=True +bEnableLint=False +bPackageDataInsideApk=True +bCreateAllPlatformsInstall=False +bDisableVerifyOBBOnStartUp=False +bForceSmallOBBFiles=False +bAllowLargeOBBFiles=False +bAllowPatchOBBFile=False +bAllowOverflowOBBFiles=False +bUseExternalFilesDir=False +bPublicLogFiles=True +Orientation=SensorLandscape +MaxAspectRatio=2.100000 +bUseDisplayCutout=False +bRestoreNotificationsOnReboot=False +bFullScreen=True +bEnableNewKeyboard=True +DepthBufferPreference=Default +bValidateTextureFormats=True +bForceCompressNativeLibs=False +bEnableAdvancedBinaryCompression=False +bEnableBundle=False +bEnableUniversalAPK=True +bBundleABISplit=True +bBundleLanguageSplit=True +bBundleDensitySplit=True +ExtraApplicationSettings= +ExtraActivitySettings= +bAndroidVoiceEnabled=False ++PackageForOculusMobile=Quest2 ++PackageForOculusMobile=Quest +bRemoveOSIG=False +KeyStore= +KeyAlias= +KeyStorePassword= +KeyPassword= +bBuildForArm64=True +bBuildForX8664=False +bBuildForES31=True +bSupportsVulkan=True +bSupportsVulkanSM5=False +DebugVulkanLayerDirectory=(Path="") +bAndroidOpenGLSupportsBackbufferSampling=False +bDetectVulkanByDefault=True +bBuildWithHiddenSymbolVisibility=False +bSaveSymbols=False +bForceLDLinker=False +bEnableGooglePlaySupport=False +bUseGetAccounts=False +GamesAppID= +bEnableSnapshots=False +bSupportAdMob=True +AdMobAdUnitID= +GooglePlayLicenseKey= +GCMClientSenderID= +bShowLaunchImage=True +bAllowIMU=False +bAllowControllers=True +bBlockAndroidKeysOnControllers=False +bControllersBlockDeviceFeedback=False +AndroidAudio=Default +AudioSampleRate=44100 +AudioCallbackBufferFrameSize=1024 +AudioNumBuffersToEnqueue=4 +AudioMaxChannels=0 +AudioNumSourceWorkers=0 +SpatializationPlugin= +ReverbPlugin= +OcclusionPlugin= +CompressionOverrides=(bOverrideCompressionTimes=False,DurationThreshold=5.000000,MaxNumRandomBranches=0,SoundCueQualityIndex=0) +CacheSizeKB=65536 +MaxChunkSizeOverrideKB=0 +bResampleForDevice=False +SoundCueCookQualityIndex=-1 +MaxSampleRate=48000.000000 +HighSampleRate=32000.000000 +MedSampleRate=24000.000000 +LowSampleRate=12000.000000 +MinSampleRate=8000.000000 +CompressionQualityModifier=1.000000 +AutoStreamingThreshold=0.000000 +AndroidGraphicsDebugger=None +MaliGraphicsDebuggerPath=(Path="") +bEnableMaliPerfCounters=False +bMultiTargetFormat_ETC2=True +bMultiTargetFormat_DXT=True +bMultiTargetFormat_ASTC=True +TextureFormatPriority_ETC2=0.200000 +TextureFormatPriority_DXT=0.600000 +TextureFormatPriority_ASTC=0.900000 +SDKAPILevelOverride= +NDKAPILevelOverride= +BuildToolsOverride= +bStreamLandscapeMeshLODs=False +bEnableDomStorage=False + diff --git a/Config/DefaultGame.ini b/Config/DefaultGame.ini new file mode 100644 index 0000000000000000000000000000000000000000..4311bd60a33c760503b20dee5215d40fcdd0bb40 --- /dev/null +++ b/Config/DefaultGame.ini @@ -0,0 +1,6 @@ + + +[/Script/EngineSettings.GeneralProjectSettings] +ProjectID=CECAFC6D4484AC9D4AE33286270F812E +bStartInVR=True + diff --git a/Config/DefaultInput.ini b/Config/DefaultInput.ini new file mode 100644 index 0000000000000000000000000000000000000000..d70ae1f47df0a0c1dd0bf74ca83ac02dc0ed779f --- /dev/null +++ b/Config/DefaultInput.ini @@ -0,0 +1,136 @@ +[/Script/Engine.InputSettings] +-AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.25,Exponent=1.f,Sensitivity=1.f)) +-AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) +-AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.f,Exponent=1.f,Sensitivity=0.07f)) ++AxisConfig=(AxisKeyName="MouseY",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseWheelAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightTriggerAxis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_Special_Left_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Mouse2D",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_LeftY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightX",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Gamepad_RightY",AxisProperties=(DeadZone=0.250000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MouseX",AxisProperties=(DeadZone=0.000000,Sensitivity=0.070000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="Vive_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="MixedReality_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="OculusTouch_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Left_Trackpad_Touch",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Grip_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trigger_Axis",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Thumbstick_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_X",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Y",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) ++AxisConfig=(AxisKeyName="ValveIndex_Right_Trackpad_Force",AxisProperties=(DeadZone=0.000000,Sensitivity=1.000000,Exponent=1.000000,bInvert=False)) +bAltEnterTogglesFullscreen=True +bF11TogglesFullscreen=True +bUseMouseForTouch=False +bEnableMouseSmoothing=True +bEnableFOVScaling=True +bCaptureMouseOnLaunch=True +bEnableLegacyInputScales=True +bEnableMotionControls=True +bFilterInputByPlatformUser=False +bShouldFlushPressedKeysOnViewportFocusLost=True +bEnableDynamicComponentInputBinding=True +bAlwaysShowTouchInterface=False +bShowConsoleOnFourFingerTap=True +bEnableGestureRecognizer=False +bUseAutocorrect=False +DefaultViewportMouseCaptureMode=CaptureDuringMouseDown +DefaultViewportMouseLockMode=LockOnCapture +FOVScale=0.011110 +DoubleClickTime=0.200000 ++ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=SpaceBar) ++ActionMappings=(ActionName="Jump",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Gamepad_FaceButton_Bottom) ++ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=R) ++ActionMappings=(ActionName="ResetVR",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_A_Click) ++ActionMappings=(ActionName="TogglePerspective",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=P) ++ActionMappings=(ActionName="TestButton",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=None) ++ActionMappings=(ActionName="MetaHuman1",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=One) ++ActionMappings=(ActionName="MetaHuman2",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Two) ++ActionMappings=(ActionName="MetaHuman3",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Three) ++ActionMappings=(ActionName="MetaHuman4",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Four) ++ActionMappings=(ActionName="GrabLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_Grip_Click) ++ActionMappings=(ActionName="GrabRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_Grip_Click) ++ActionMappings=(ActionName="LeftClick",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Left_Trigger_Click) ++ActionMappings=(ActionName="RightClick",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_Trigger_Click) ++ActionMappings=(ActionName="RightBPress",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_B_Click) ++ActionMappings=(ActionName="LeftMouseClick",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=LeftMouseButton) ++ActionMappings=(ActionName="RightMouseClick",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=RightMouseButton) ++ActionMappings=(ActionName="GrabLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=Vive_Left_Grip_Click) ++ActionMappings=(ActionName="GrabLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=MixedReality_Left_Grip_Click) ++ActionMappings=(ActionName="GrabLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OpenXRMsftHandInteraction_Left_Grip_Axis) ++ActionMappings=(ActionName="GrabLeft",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusHand_Left_ThumbPinch) ++ActionMappings=(ActionName="RightAPress",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_A_Click) ++ActionMappings=(ActionName="MenuUp",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_Thumbstick_Up) ++ActionMappings=(ActionName="MenuDown",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusTouch_Right_Thumbstick_Down) ++ActionMappings=(ActionName="GrabRight",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=OculusHand_Right_ThumbPinch) ++ActionMappings=(ActionName="LeftClick",bShift=False,bCtrl=False,bAlt=False,bCmd=False,Key=None) ++AxisMappings=(AxisName="TurnRightMouse",Scale=1.000000,Key=MouseX) ++AxisMappings=(AxisName="LookUpMouse",Scale=-1.000000,Key=MouseY) ++AxisMappings=(AxisName="MoveForwardGamepad_Y",Scale=1.000000,Key=OculusTouch_Left_Thumbstick_Y) ++AxisMappings=(AxisName="MoveRightGamepad_X",Scale=1.000000,Key=OculusTouch_Left_Thumbstick_X) ++AxisMappings=(AxisName="TurnRighttGamepad",Scale=0.000000,Key=OculusTouch_Right_Thumbstick_Y) ++AxisMappings=(AxisName="LookUpGamepad",Scale=1.000000,Key=OculusTouch_Right_Thumbstick_Y) ++AxisMappings=(AxisName="MoveUpDown",Scale=-1.000000,Key=E) ++AxisMappings=(AxisName="MoveUpDown",Scale=1.000000,Key=Q) ++AxisMappings=(AxisName="MoveRate",Scale=1.000000,Key=MouseWheelAxis) ++AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=W) ++AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=D) ++AxisMappings=(AxisName="MoveForward",Scale=-1.000000,Key=S) ++AxisMappings=(AxisName="MoveRight",Scale=-1.000000,Key=A) ++AxisMappings=(AxisName="MoveRight",Scale=1.000000,Key=Right) ++AxisMappings=(AxisName="MoveRight",Scale=-1.000000,Key=Left) ++AxisMappings=(AxisName="MoveForward",Scale=1.000000,Key=Up) ++AxisMappings=(AxisName="MoveForward",Scale=-1.000000,Key=Down) ++AxisMappings=(AxisName="MoveForwardGamepad_Y",Scale=0.000000,Key=OculusTouch_Left_Thumbstick_X) ++AxisMappings=(AxisName="MoveRightGamepad_X",Scale=0.000000,Key=OculusTouch_Left_Thumbstick_Y) ++AxisMappings=(AxisName="LookUpGamepad",Scale=0.000000,Key=OculusTouch_Right_Thumbstick_X) ++AxisMappings=(AxisName="TurnRighttGamepad",Scale=1.000000,Key=OculusTouch_Right_Thumbstick_X) ++AxisMappings=(AxisName="MoveUpGamepad_Y",Scale=0.000000,Key=OculusTouch_Right_Thumbstick_X) ++AxisMappings=(AxisName="MoveUpGamepad_Y",Scale=1.000000,Key=OculusTouch_Right_Thumbstick_Y) ++SpeechMappings=(ActionName="jumpSound",SpeechKeyword="jump") +DefaultPlayerInputClass=/Script/EnhancedInput.EnhancedPlayerInput +DefaultInputComponentClass=/Script/EnhancedInput.EnhancedInputComponent +DefaultTouchInterface=None +-ConsoleKeys=Tilde ++ConsoleKeys=Tilde + diff --git a/Config/HoloLens/HoloLensEngine.ini b/Config/HoloLens/HoloLensEngine.ini new file mode 100644 index 0000000000000000000000000000000000000000..7ace857b00b42e8c40aa85376781c8b6ff0ab575 --- /dev/null +++ b/Config/HoloLens/HoloLensEngine.ini @@ -0,0 +1,31 @@ + + +[/Script/HoloLensPlatformEditor.HoloLensTargetSettings] +bBuildForEmulation=False +bBuildForDevice=True +bUseNameForLogo=True +bBuildForRetailWindowsStore=False +bAutoIncrementVersion=False +bShouldCreateAppInstaller=False +AppInstallerInstallationURL= +HoursBetweenUpdateChecks=0 +bEnablePIXProfiling=False +TileBackgroundColor=(B=64,G=0,R=0,A=255) +SplashScreenBackgroundColor=(B=64,G=0,R=0,A=255) ++PerCultureResources=(CultureId="",Strings=(PackageDisplayName="",PublisherDisplayName="",PackageDescription="",ApplicationDisplayName="",ApplicationDescription=""),Images=()) +TargetDeviceFamily=Windows.Holographic +MinimumPlatformVersion= +MaximumPlatformVersionTested=10.0.18362.0 +MaxTrianglesPerCubicMeter=500.000000 +SpatialMeshingVolumeSize=20.000000 +CompilerVersion=Default +Windows10SDKVersion=10.0.18362.0 ++CapabilityList=internetClientServer ++CapabilityList=privateNetworkClientServer ++Uap2CapabilityList=spatialPerception +bSetDefaultCapabilities=False +SpatializationPlugin= +ReverbPlugin= +OcclusionPlugin= +SoundCueCookQualityIndex=-1 + diff --git a/Config/SteamVRBindings/gamepad.json b/Config/SteamVRBindings/gamepad.json new file mode 100644 index 0000000000000000000000000000000000000000..d8458b88c58d1926ccd96e540647ede625dff8d7 --- /dev/null +++ b/Config/SteamVRBindings/gamepad.json @@ -0,0 +1,13 @@ +{ + "name": "Default bindings for Gamepads", + "controller_type": "gamepad", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/holographic_controller.json b/Config/SteamVRBindings/holographic_controller.json new file mode 100644 index 0000000000000000000000000000000000000000..008ac4d879f83e3d30e8b462e3e49fc9adf4af5f --- /dev/null +++ b/Config/SteamVRBindings/holographic_controller.json @@ -0,0 +1,44 @@ +{ + "name": "Default bindings for MixedReality", + "controller_type": "holographic_controller", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [], + "poses": [ + { + "output": "/actions/main/in/controllerleft", + "path": "/user/hand/left/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/controllerright", + "path": "/user/hand/right/pose/raw" + } + ], + "skeleton": [ + { + "output": "/actions/main/in/skeletonleft", + "path": "/user/hand/left/input/skeleton/left" + }, + { + "output": "/actions/main/in/skeletonright", + "path": "/user/hand/right/input/skeleton/right" + } + ], + "haptics": [ + { + "output": "/actions/main/out/vibrateleft", + "path": "/user/hand/left/output/haptic" + }, + { + "output": "/actions/main/out/vibrateright", + "path": "/user/hand/right/output/haptic" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/hpmotioncontroller.json b/Config/SteamVRBindings/hpmotioncontroller.json new file mode 100644 index 0000000000000000000000000000000000000000..570a15b6c0e2ffa9a46ebc008a951513eb95ef11 --- /dev/null +++ b/Config/SteamVRBindings/hpmotioncontroller.json @@ -0,0 +1,44 @@ +{ + "name": "Default bindings for HPMixedRealityController", + "controller_type": "hpmotioncontroller", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [], + "poses": [ + { + "output": "/actions/main/in/controllerleft", + "path": "/user/hand/left/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/controllerright", + "path": "/user/hand/right/pose/raw" + } + ], + "skeleton": [ + { + "output": "/actions/main/in/skeletonleft", + "path": "/user/hand/left/input/skeleton/left" + }, + { + "output": "/actions/main/in/skeletonright", + "path": "/user/hand/right/input/skeleton/right" + } + ], + "haptics": [ + { + "output": "/actions/main/out/vibrateleft", + "path": "/user/hand/left/output/haptic" + }, + { + "output": "/actions/main/out/vibrateright", + "path": "/user/hand/right/output/haptic" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/indexhmd.json b/Config/SteamVRBindings/indexhmd.json new file mode 100644 index 0000000000000000000000000000000000000000..ce4186162a7a07b629bfc2184097d306e7203327 --- /dev/null +++ b/Config/SteamVRBindings/indexhmd.json @@ -0,0 +1,13 @@ +{ + "name": "Default bindings for Valve Index Headset", + "controller_type": "indexhmd", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/knuckles.json b/Config/SteamVRBindings/knuckles.json new file mode 100644 index 0000000000000000000000000000000000000000..83bd94ab62c37003108afc81b7dabc980fa27b42 --- /dev/null +++ b/Config/SteamVRBindings/knuckles.json @@ -0,0 +1,44 @@ +{ + "name": "Default bindings for ValveIndex", + "controller_type": "knuckles", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [], + "poses": [ + { + "output": "/actions/main/in/controllerleft", + "path": "/user/hand/left/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/controllerright", + "path": "/user/hand/right/pose/raw" + } + ], + "skeleton": [ + { + "output": "/actions/main/in/skeletonleft", + "path": "/user/hand/left/input/skeleton/left" + }, + { + "output": "/actions/main/in/skeletonright", + "path": "/user/hand/right/input/skeleton/right" + } + ], + "haptics": [ + { + "output": "/actions/main/out/vibrateleft", + "path": "/user/hand/left/output/haptic" + }, + { + "output": "/actions/main/out/vibrateright", + "path": "/user/hand/right/output/haptic" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/oculus_touch.json b/Config/SteamVRBindings/oculus_touch.json new file mode 100644 index 0000000000000000000000000000000000000000..e3e322634aa970c25d2c0191a0598f82b4c650a6 --- /dev/null +++ b/Config/SteamVRBindings/oculus_touch.json @@ -0,0 +1,133 @@ +{ + "name": "Default bindings for OculusTouch", + "controller_type": "oculus_touch", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [ + { + "mode": "button", + "path": "/user/hand/right/input/a", + "inputs": + { + "click": + { + "output": "/actions/main/in/ResetVR" + } + } + }, + { + "mode": "trigger", + "path": "/user/hand/left/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/GrabLeft" + } + } + }, + { + "mode": "trigger", + "path": "/user/hand/right/input/grip", + "inputs": + { + "click": + { + "output": "/actions/main/in/GrabRight" + } + } + }, + { + "mode": "trigger", + "path": "/user/hand/left/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/LeftClick" + } + } + }, + { + "mode": "trigger", + "path": "/user/hand/right/input/trigger", + "inputs": + { + "click": + { + "output": "/actions/main/in/RightClick" + } + } + }, + { + "mode": "button", + "path": "/user/hand/right/input/b", + "inputs": + { + "click": + { + "output": "/actions/main/in/RightBPress" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/left/input/joystick", + "inputs": + { + "position": + { + "output": "/actions/main/in/MoveForwardGamepad_Y,MoveForwardGamepad_Y X Y_axis2d" + } + } + }, + { + "mode": "joystick", + "path": "/user/hand/right/input/joystick", + "inputs": + { + "position": + { + "output": "/actions/main/in/TurnRighttGamepad,TurnRighttGamepad X Y_axis2d" + } + } + } + ], + "poses": [ + { + "output": "/actions/main/in/controllerleft", + "path": "/user/hand/left/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/controllerright", + "path": "/user/hand/right/pose/raw" + } + ], + "skeleton": [ + { + "output": "/actions/main/in/skeletonleft", + "path": "/user/hand/left/input/skeleton/left" + }, + { + "output": "/actions/main/in/skeletonright", + "path": "/user/hand/right/input/skeleton/right" + } + ], + "haptics": [ + { + "output": "/actions/main/out/vibrateleft", + "path": "/user/hand/left/output/haptic" + }, + { + "output": "/actions/main/out/vibrateright", + "path": "/user/hand/right/output/haptic" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/rift.json b/Config/SteamVRBindings/rift.json new file mode 100644 index 0000000000000000000000000000000000000000..68060ea43710f591d0d9b3c5eb7e13367a904205 --- /dev/null +++ b/Config/SteamVRBindings/rift.json @@ -0,0 +1,13 @@ +{ + "name": "Default bindings for Rift Headset", + "controller_type": "rift", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/steamvr_manifest.json b/Config/SteamVRBindings/steamvr_manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..be11517643db736a7dba02038f25b97a837a98f9 --- /dev/null +++ b/Config/SteamVRBindings/steamvr_manifest.json @@ -0,0 +1,364 @@ +{ + "actions": [ + { + "name": "/actions/main/in/controllerleft", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/controllerright", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_camera", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_chest", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_shoulder_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_shoulder_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_elbow_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_elbow_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_knee_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_knee_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_waist", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_foot_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_foot_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_keyboard", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_pose_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_pose_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_back_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_back_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_front_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_front_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_frontr_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_frontr_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_grip_left", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/tracker_handed_grip_right", + "type": "pose", + "requirement": "optional" + }, + { + "name": "/actions/main/in/skeletonleft", + "type": "skeleton", + "skeleton": "/skeleton/hand/left", + "requirement": "optional" + }, + { + "name": "/actions/main/in/skeletonright", + "type": "skeleton", + "skeleton": "/skeleton/hand/right", + "requirement": "optional" + }, + { + "name": "/actions/main/out/vibrateleft", + "type": "vibration", + "requirement": "optional" + }, + { + "name": "/actions/main/out/vibrateright", + "type": "vibration", + "requirement": "optional" + }, + { + "name": "/actions/main/in/open_console", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/main/in/ResetVR", + "type": "boolean" + }, + { + "name": "/actions/main/in/GrabLeft", + "type": "boolean" + }, + { + "name": "/actions/main/in/GrabRight", + "type": "boolean" + }, + { + "name": "/actions/main/in/LeftClick", + "type": "boolean" + }, + { + "name": "/actions/main/in/RightClick", + "type": "boolean" + }, + { + "name": "/actions/main/in/RightBPress", + "type": "boolean" + }, + { + "name": "/actions/main/in/TurnRightMouse axis", + "type": "vector1" + }, + { + "name": "/actions/main/in/LookUpMouse axis", + "type": "vector1" + }, + { + "name": "/actions/main/in/MoveForwardGamepad_Y,MoveForwardGamepad_Y X Y_axis2d", + "type": "vector2" + }, + { + "name": "/actions/main/in/TurnRighttGamepad,TurnRighttGamepad X Y_axis2d", + "type": "vector2" + }, + { + "name": "/actions/main/in/MoveUpDown axis", + "type": "vector1" + }, + { + "name": "/actions/main/in/MoveRate axis", + "type": "vector1" + }, + { + "name": "/actions/main/in/MoveForward axis", + "type": "vector1" + }, + { + "name": "/actions/main/in/MoveRight axis", + "type": "vector1" + } + ], + "action_sets": [ + { + "name": "/actions/main", + "usage": "leftright" + } + ], + "default_bindings": [ + { + "controller_type": "knuckles", + "binding_url": "knuckles.json" + }, + { + "controller_type": "vive_controller", + "binding_url": "vive_controller.json" + }, + { + "controller_type": "vive_cosmos_controller", + "binding_url": "vive_cosmos_controller.json" + }, + { + "controller_type": "oculus_touch", + "binding_url": "oculus_touch.json" + }, + { + "controller_type": "holographic_controller", + "binding_url": "holographic_controller.json" + }, + { + "controller_type": "hpmotioncontroller", + "binding_url": "hpmotioncontroller.json" + }, + { + "controller_type": "indexhmd", + "binding_url": "indexhmd.json" + }, + { + "controller_type": "vive", + "binding_url": "vive.json" + }, + { + "controller_type": "vive_pro", + "binding_url": "vive_pro.json" + }, + { + "controller_type": "rift", + "binding_url": "rift.json" + }, + { + "controller_type": "vive_tracker", + "binding_url": "vive_tracker.json" + }, + { + "controller_type": "vive_tracker_camera", + "binding_url": "vive_tracker_camera.json" + }, + { + "controller_type": "vive_tracker_waist", + "binding_url": "vive_tracker_waist.json" + }, + { + "controller_type": "vive_tracker_left_foot", + "binding_url": "vive_tracker_left_foot.json" + }, + { + "controller_type": "vive_tracker_right_foot", + "binding_url": "vive_tracker_right_foot.json" + }, + { + "controller_type": "vive_tracker_left_shoulder", + "binding_url": "vive_tracker_left_shoulder.json" + }, + { + "controller_type": "vive_tracker_right_shoulder", + "binding_url": "vive_tracker_right_shoulder.json" + }, + { + "controller_type": "vive_tracker_left_elbow", + "binding_url": "vive_tracker_left_elbow.json" + }, + { + "controller_type": "vive_tracker_right_elbow", + "binding_url": "vive_tracker_right_elbow.json" + }, + { + "controller_type": "vive_tracker_left_knee", + "binding_url": "vive_tracker_left_knee.json" + }, + { + "controller_type": "vive_tracker_right_knee", + "binding_url": "vive_tracker_right_knee.json" + }, + { + "controller_type": "vive_tracker_chest", + "binding_url": "vive_tracker_chest.json" + }, + { + "controller_type": "vive_tracker_keyboard", + "binding_url": "vive_tracker_keyboard.json" + }, + { + "controller_type": "vive_tracker_handed", + "binding_url": "vive_tracker_handed.json" + }, + { + "controller_type": "gamepad", + "binding_url": "gamepad.json" + } + ], + "localization": [ + { + "language_tag": "en_us", + "/actions/main/in/controllerleft": "Left Controller [Pose]", + "/actions/main/in/controllerright": "Right Controller [Pose]", + "/actions/main/in/tracker_camera": "Camera [Tracker]", + "/actions/main/in/tracker_chest": "Chest [Tracker]", + "/actions/main/in/tracker_shoulder_left": "Shoulder Left [Tracker]", + "/actions/main/in/tracker_shoulder_right": "Shoulder Right [Tracker]", + "/actions/main/in/tracker_elbow_left": "Elbow Left [Tracker]", + "/actions/main/in/tracker_elbow_right": "Elbow Right [Tracker]", + "/actions/main/in/tracker_knee_left": "Knee Left [Tracker]", + "/actions/main/in/tracker_knee_right": "Knee Right [Tracker]", + "/actions/main/in/tracker_waist": "Waist [Tracker]", + "/actions/main/in/tracker_foot_left": "Foot Left [Tracker]", + "/actions/main/in/tracker_foot_right": "Foot Right [Tracker]", + "/actions/main/in/tracker_keyboard": "Keyboard [Tracker]", + "/actions/main/in/tracker_handed_pose_left": "Raw Pose Left [Tracker]", + "/actions/main/in/tracker_handed_pose_right": "Raw Pose Right [Tracker]", + "/actions/main/in/tracker_handed_back_left": "Handed Back Left [Tracker]", + "/actions/main/in/tracker_handed_back_right": "Handed Back Right [Tracker]", + "/actions/main/in/tracker_handed_front_left": "Handed Front Left [Tracker]", + "/actions/main/in/tracker_handed_front_right": "Handed Front Right [Tracker]", + "/actions/main/in/tracker_handed_frontr_left": "Handed Front Rolled Left [Tracker]", + "/actions/main/in/tracker_handed_frontr_right": "Handed Front Rolled Right [Tracker]", + "/actions/main/in/tracker_handed_grip_left": "Handed Pistol Grip Left [Tracker]", + "/actions/main/in/tracker_handed_grip_right": "Handed Pistol Grip Right [Tracker]", + "/actions/main/in/skeletonleft": "Skeleton (Left)", + "/actions/main/in/skeletonright": "Skeleton (Right)", + "/actions/main/out/vibrateleft": "Haptic (Left)", + "/actions/main/out/vibrateright": "Haptic (Right)", + "/actions/main/in/open_console": "Open Console", + "/actions/main/in/ResetVR": "ResetVR", + "/actions/main/in/GrabLeft": "GrabLeft", + "/actions/main/in/GrabRight": "GrabRight", + "/actions/main/in/LeftClick": "LeftClick", + "/actions/main/in/RightClick": "RightClick", + "/actions/main/in/RightBPress": "RightBPress", + "/actions/main/in/TurnRightMouse axis": "TurnRightMouse", + "/actions/main/in/LookUpMouse axis": "LookUpMouse", + "/actions/main/in/MoveForwardGamepad_Y,MoveForwardGamepad_Y X Y_axis2d": "MoveForwardGamepad_Y,MoveForwardGamepad_Y X Y_axis2d", + "/actions/main/in/TurnRighttGamepad,TurnRighttGamepad X Y_axis2d": "TurnRighttGamepad,TurnRighttGamepad X Y_axis2d", + "/actions/main/in/MoveUpDown axis": "MoveUpDown", + "/actions/main/in/MoveRate axis": "MoveRate", + "/actions/main/in/MoveForward axis": "MoveForward", + "/actions/main/in/MoveRight axis": "MoveRight", + "/actions/main": "Main Game Actions" + } + ] +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive.json b/Config/SteamVRBindings/vive.json new file mode 100644 index 0000000000000000000000000000000000000000..507da5792ddde4c202fd809da8200d2b71df406d --- /dev/null +++ b/Config/SteamVRBindings/vive.json @@ -0,0 +1,13 @@ +{ + "name": "Default bindings for Vive Headset", + "controller_type": "vive", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_controller.json b/Config/SteamVRBindings/vive_controller.json new file mode 100644 index 0000000000000000000000000000000000000000..db8e072edd50e21199fcf71a2d3e3341c9a5da57 --- /dev/null +++ b/Config/SteamVRBindings/vive_controller.json @@ -0,0 +1,44 @@ +{ + "name": "Default bindings for Vive", + "controller_type": "vive_controller", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [], + "poses": [ + { + "output": "/actions/main/in/controllerleft", + "path": "/user/hand/left/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/controllerright", + "path": "/user/hand/right/pose/raw" + } + ], + "skeleton": [ + { + "output": "/actions/main/in/skeletonleft", + "path": "/user/hand/left/input/skeleton/left" + }, + { + "output": "/actions/main/in/skeletonright", + "path": "/user/hand/right/input/skeleton/right" + } + ], + "haptics": [ + { + "output": "/actions/main/out/vibrateleft", + "path": "/user/hand/left/output/haptic" + }, + { + "output": "/actions/main/out/vibrateright", + "path": "/user/hand/right/output/haptic" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_cosmos_controller.json b/Config/SteamVRBindings/vive_cosmos_controller.json new file mode 100644 index 0000000000000000000000000000000000000000..8c56883ff73883d4edd7987bd12aaf441563ba3b --- /dev/null +++ b/Config/SteamVRBindings/vive_cosmos_controller.json @@ -0,0 +1,44 @@ +{ + "name": "Default bindings for Cosmos", + "controller_type": "vive_cosmos_controller", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [], + "poses": [ + { + "output": "/actions/main/in/controllerleft", + "path": "/user/hand/left/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/controllerright", + "path": "/user/hand/right/pose/raw" + } + ], + "skeleton": [ + { + "output": "/actions/main/in/skeletonleft", + "path": "/user/hand/left/input/skeleton/left" + }, + { + "output": "/actions/main/in/skeletonright", + "path": "/user/hand/right/input/skeleton/right" + } + ], + "haptics": [ + { + "output": "/actions/main/out/vibrateleft", + "path": "/user/hand/left/output/haptic" + }, + { + "output": "/actions/main/out/vibrateright", + "path": "/user/hand/right/output/haptic" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_pro.json b/Config/SteamVRBindings/vive_pro.json new file mode 100644 index 0000000000000000000000000000000000000000..b8d81b59f728030396c8c2103d61c86d72314f20 --- /dev/null +++ b/Config/SteamVRBindings/vive_pro.json @@ -0,0 +1,13 @@ +{ + "name": "Default bindings for Vive Pro Headset", + "controller_type": "vive_pro", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "sources": [] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker.json b/Config/SteamVRBindings/vive_tracker.json new file mode 100644 index 0000000000000000000000000000000000000000..1e11b00694d0cc569e64d2b1e148cf3ff94e9a24 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker.json @@ -0,0 +1,64 @@ +{ + "name": "Default bindings for Vive Tracker", + "controller_type": "vive_tracker", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_handed_pose_left", + "path": "/user/hand/left/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_pose_right", + "path": "/user/hand/right/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_back_left", + "path": "/user/hand/left/pose/back", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_back_right", + "path": "/user/hand/right/pose/back", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_front_left", + "path": "/user/hand/left/pose/front", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_front_right", + "path": "/actions/main/in/tracker_handed_front_right", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_frontr_left", + "path": "/user/hand/left/pose/front_rolled", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_frontr_right", + "path": "/user/hand/right/pose/front_rolled", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_grip_left", + "path": "/user/hand/left/pose/pistol", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_grip_right", + "path": "/user/hand/right/pose/pistol", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_camera.json b/Config/SteamVRBindings/vive_tracker_camera.json new file mode 100644 index 0000000000000000000000000000000000000000..3355fe6be76b31afdacc326f53d1c95e94943133 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_camera.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Camera)", + "controller_type": "vive_tracker_camera", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_camera", + "path": "/user/camera/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_chest.json b/Config/SteamVRBindings/vive_tracker_chest.json new file mode 100644 index 0000000000000000000000000000000000000000..df8b361ce6b91f99390bf9ef5a69ab996703b2b1 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_chest.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Chest)", + "controller_type": "vive_tracker_chest", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_chest", + "path": "/user/chest/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_handed.json b/Config/SteamVRBindings/vive_tracker_handed.json new file mode 100644 index 0000000000000000000000000000000000000000..b1b8df4ee920f8c1a6a905782475efa16e55d207 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_handed.json @@ -0,0 +1,64 @@ +{ + "name": "Default bindings for Vive Tracker (Handed)", + "controller_type": "vive_tracker_handed", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_handed_pose_left", + "path": "/user/hand/left/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_pose_right", + "path": "/user/hand/right/pose/raw", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_back_left", + "path": "/user/hand/left/pose/back", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_back_right", + "path": "/user/hand/right/pose/back", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_front_left", + "path": "/user/hand/left/pose/front", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_front_right", + "path": "/actions/main/in/tracker_handed_front_right", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_frontr_left", + "path": "/user/hand/left/pose/front_rolled", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_frontr_right", + "path": "/user/hand/right/pose/front_rolled", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_grip_left", + "path": "/user/hand/left/pose/pistol", + "requirement": "optional" + }, + { + "output": "/actions/main/in/tracker_handed_grip_right", + "path": "/user/hand/right/pose/pistol", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_keyboard.json b/Config/SteamVRBindings/vive_tracker_keyboard.json new file mode 100644 index 0000000000000000000000000000000000000000..c74399b743741bcea0cd704309c9c5f74a65fae5 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_keyboard.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Keyboard)", + "controller_type": "vive_tracker_keyboard", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_keyboard", + "path": "/user/keyboard/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_left_elbow.json b/Config/SteamVRBindings/vive_tracker_left_elbow.json new file mode 100644 index 0000000000000000000000000000000000000000..cb3b689228f21024c89fd1d0d8c33eb92769328a --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_left_elbow.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Left Elbow)", + "controller_type": "vive_tracker_left_elbow", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_elbow_left", + "path": "/user/elbow/left/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_left_foot.json b/Config/SteamVRBindings/vive_tracker_left_foot.json new file mode 100644 index 0000000000000000000000000000000000000000..0d3db93e20ce149b982d4c147acefef65339c80d --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_left_foot.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Left Foot)", + "controller_type": "vive_tracker_left_foot", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_foot_left", + "path": "/user/foot/left/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_left_knee.json b/Config/SteamVRBindings/vive_tracker_left_knee.json new file mode 100644 index 0000000000000000000000000000000000000000..38b2ce892ce869ef649f28dbe8749feb68032bc1 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_left_knee.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Left knee)", + "controller_type": "vive_tracker_left_knee", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_knee_left", + "path": "/user/knee/left/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_left_shoulder.json b/Config/SteamVRBindings/vive_tracker_left_shoulder.json new file mode 100644 index 0000000000000000000000000000000000000000..299532350af1b53ddf84bfa6ada7beec26f26c06 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_left_shoulder.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Left Shoulder)", + "controller_type": "vive_tracker_left_shoulder", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_shoulder_left", + "path": "/user/shoulder/left/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_right_elbow.json b/Config/SteamVRBindings/vive_tracker_right_elbow.json new file mode 100644 index 0000000000000000000000000000000000000000..2f3f643e232822f8997889e8d38152a4ff2e4a90 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_right_elbow.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Right Elbow)", + "controller_type": "vive_tracker_right_elbow", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_elbow_right", + "path": "/user/elbow/right/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_right_foot.json b/Config/SteamVRBindings/vive_tracker_right_foot.json new file mode 100644 index 0000000000000000000000000000000000000000..4541383e23522a5be330e2f51128cf322efbedb3 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_right_foot.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Right Foot)", + "controller_type": "vive_tracker_right_foot", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_foot_right", + "path": "/user/foot/right/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_right_knee.json b/Config/SteamVRBindings/vive_tracker_right_knee.json new file mode 100644 index 0000000000000000000000000000000000000000..d80ee8bb00d9de767974894ab8efbadba32d0df7 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_right_knee.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Right Knee)", + "controller_type": "vive_tracker_right_knee", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_knee_right", + "path": "/user/knee/right/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_right_shoulder.json b/Config/SteamVRBindings/vive_tracker_right_shoulder.json new file mode 100644 index 0000000000000000000000000000000000000000..6d66362d30006abb01ac98bb6f58c07a7c038a22 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_right_shoulder.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Right Shoulder)", + "controller_type": "vive_tracker_right_shoulder", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_shoulder_right", + "path": "/user/shoulder/right/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/SteamVRBindings/vive_tracker_waist.json b/Config/SteamVRBindings/vive_tracker_waist.json new file mode 100644 index 0000000000000000000000000000000000000000..bd306004e89f2529455b21a036f98e89ae547c24 --- /dev/null +++ b/Config/SteamVRBindings/vive_tracker_waist.json @@ -0,0 +1,19 @@ +{ + "name": "Default bindings for Vive Tracker (Waist)", + "controller_type": "vive_tracker_waist", + "last_edited_by": "UnrealEngine", + "bindings": + { + "/actions/main": + { + "poses": [ + { + "output": "/actions/main/in/tracker_waist", + "path": "/user/waist/pose/raw", + "requirement": "optional" + } + ] + } + }, + "description": "" +} \ No newline at end of file diff --git a/Config/steamvr_ue_editor_app.json b/Config/steamvr_ue_editor_app.json new file mode 100644 index 0000000000000000000000000000000000000000..d596e9c16ef46ac4f0309623651dd63cfeaac989 --- /dev/null +++ b/Config/steamvr_ue_editor_app.json @@ -0,0 +1,18 @@ +{ + "source": "UE", + "applications": [ + { + "app_key": "application.generated.ue.cyberproject-20979098.unrealeditor.exe", + "launch_type": "url", + "url": "steam://launch/", + "action_manifest_path": "C:/UnrealProjects/CyberProject/Config/SteamVRBindings/steamvr_manifest.json", + "strings": + { + "en_us": + { + "name": "CyberProject-20979098 [UE Editor]" + } + } + } + ] +} \ No newline at end of file diff --git a/CyberProject.uproject b/CyberProject.uproject new file mode 100644 index 0000000000000000000000000000000000000000..1a66132cf3279106f34f91fd008db6c1b2c17bb2 --- /dev/null +++ b/CyberProject.uproject @@ -0,0 +1,131 @@ +{ + "FileVersion": 3, + "EngineAssociation": "5.1", + "Category": "", + "Description": "", + "Modules": [ + { + "Name": "CyberProject", + "Type": "Runtime", + "LoadingPhase": "Default" + } + ], + "Plugins": [ + { + "Name": "ModelingToolsEditorMode", + "Enabled": true, + "TargetAllowList": [ + "Editor" + ] + }, + { + "Name": "RigLogic", + "Enabled": true + }, + { + "Name": "LiveLink", + "Enabled": true + }, + { + "Name": "LiveLinkControlRig", + "Enabled": true + }, + { + "Name": "AppleARKitFaceSupport", + "Enabled": true, + "SupportedTargetPlatforms": [ + "IOS", + "Win64", + "Mac" + ] + }, + { + "Name": "OpenXR", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android", + "HoloLens" + ] + }, + { + "Name": "OpenXREyeTracker", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android", + "HoloLens" + ] + }, + { + "Name": "OpenXRHandTracking", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Linux", + "Android", + "HoloLens" + ] + }, + { + "Name": "OculusXR", + "Enabled": true, + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/8313d8d7e7cf4e03a33e79eb757bccba", + "SupportedTargetPlatforms": [ + "Win64", + "Android" + ] + }, + { + "Name": "AESGCMHandlerComponent", + "Enabled": true + }, + { + "Name": "XRVisualization", + "Enabled": true + }, + { + "Name": "OpenXRMsftHandInteraction", + "Enabled": true, + "SupportedTargetPlatforms": [ + "Win64", + "Android", + "HoloLens" + ] + }, + { + "Name": "WebUI", + "Enabled": true + }, + { + "Name": "JsonLibrary", + "Enabled": true + }, + { + "Name": "HttpLibrary", + "Enabled": true + }, + { + "Name": "HairStrands", + "Enabled": true + }, + { + "Name": "AlembicHairImporter", + "Enabled": true + }, + { + "Name": "CesiumForUnreal", + "Enabled": true, + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/content/87b0d05800a545d49bf858ef3458c4f7", + "SupportedTargetPlatforms": [ + "Win64", + "Mac", + "Linux", + "Android", + "IOS" + ] + } + ] +} \ No newline at end of file diff --git a/Plugins/CyberArchWarehouse/Content/Artifacts/MeshMaterial.uasset b/Plugins/CyberArchWarehouse/Content/Artifacts/MeshMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..8ecc6a1be4c7f62e0a570cd74a04f66be53fe194 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/Artifacts/MeshMaterial.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/Artifacts/rollertest.uasset b/Plugins/CyberArchWarehouse/Content/Artifacts/rollertest.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1b71b5155d938b426647876133b802b7667dfebf Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/Artifacts/rollertest.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/Maps/MainMap.umap b/Plugins/CyberArchWarehouse/Content/Maps/MainMap.umap new file mode 100644 index 0000000000000000000000000000000000000000..f232ae73de22c79e7fc7c3e6ae140931cdb86a5c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/Maps/MainMap.umap differ diff --git a/Plugins/CyberArchWarehouse/Content/Maps/MainMap_BuiltData.uasset b/Plugins/CyberArchWarehouse/Content/Maps/MainMap_BuiltData.uasset new file mode 100644 index 0000000000000000000000000000000000000000..28b95b50feafe16f6dcb07af8d8e56050041bf38 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/Maps/MainMap_BuiltData.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/BP_SpatialAnchorModel.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/BP_SpatialAnchorModel.uasset new file mode 100644 index 0000000000000000000000000000000000000000..53f315c611dbd184a5837059c49d4cb173a0462e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/BP_SpatialAnchorModel.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/BP_VRPawn.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/BP_VRPawn.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3d7b085ad634572a7c6d6223b72211d736e270bc Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/BP_VRPawn.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/Components/BP_MenuManagerComponent.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/Components/BP_MenuManagerComponent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..257ee5be0d547d608b960d5990492e735691c43e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/Components/BP_MenuManagerComponent.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/Components/BP_SpatialAnchorManagerComponent.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/Components/BP_SpatialAnchorManagerComponent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c1b077490b8c647cf737aa2d1c088be7dbce8f2b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/Components/BP_SpatialAnchorManagerComponent.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/HUDBP.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/HUDBP.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5f95feab95e7f4a6d46b5b388714db0208b7eabf Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/HUDBP.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/PC_XR.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/PC_XR.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1c09374b0d5372fea70e7535351227102387ee6d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/PC_XR.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/SaveGame/BP_AnchorSaveGame.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/SaveGame/BP_AnchorSaveGame.uasset new file mode 100644 index 0000000000000000000000000000000000000000..039aaaa59a0d8ed6e4d79fe23ca6f17e9c142554 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/SaveGame/BP_AnchorSaveGame.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/SaveGame/Struct_SavedAnchor.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/SaveGame/Struct_SavedAnchor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..2eaac88de791458a26589fa5a147f1cff35c12d7 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/SaveGame/Struct_SavedAnchor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_AnchorInfo.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_AnchorInfo.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e416d624cdadc2f21c3ac5fed4339a261010fce8 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_AnchorInfo.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_BROWSER.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_BROWSER.uasset new file mode 100644 index 0000000000000000000000000000000000000000..fc15ea009eff9e6f82e63252ed4793ed8a59ceab Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_BROWSER.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a8064c15367e33f4693db6e7c6d71e54c0f83834 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_MenuItem.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_MenuItem.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b44741bcf0863a9a315946199055138f74468e5c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_MenuItem.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu_Anchor.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu_Anchor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..14566af7426e31c2160d6d2b86e40b1a2a79eff8 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu_Anchor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu_Main.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu_Main.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4d266ed521e7f056875608098415f7e36c69d93b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/BP_Menu_Main.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough.uasset new file mode 100644 index 0000000000000000000000000000000000000000..40ded45350fcb4e72c0fad4a2006804b05005541 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough_Masked_OneSided.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough_Masked_OneSided.uasset new file mode 100644 index 0000000000000000000000000000000000000000..43db84f2b127214aa2c5a5f5b696dc744d585e10 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough_Masked_OneSided.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough_Opaque.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough_Opaque.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6fb0e2eee498336bd4f3996001a6b69c76e45f75 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/Widget3DPassThrough_Opaque.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/widget3d.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/widget3d.uasset new file mode 100644 index 0000000000000000000000000000000000000000..36f20e6331031a4f7aa431754bfa8705fee4f2ab Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/UI/widget3d.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/gamear.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/gamear.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5e57fb13f9e85485d4546ab1fadb3b0c7acb55eb Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Blueprints/gamear.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Maps/SA_Showcase.umap b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Maps/SA_Showcase.umap new file mode 100644 index 0000000000000000000000000000000000000000..622617070416e5a2d8314da368db433538fa2819 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Maps/SA_Showcase.umap differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Maps/SA_Showcase_BuiltData.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Maps/SA_Showcase_BuiltData.uasset new file mode 100644 index 0000000000000000000000000000000000000000..42f5b0e04858e452271be604d5b6aa4c6215d9bf Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Maps/SA_Showcase_BuiltData.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/DefaultTextMaterialEmissive.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/DefaultTextMaterialEmissive.uasset new file mode 100644 index 0000000000000000000000000000000000000000..63604b3dd364db38bb9e72751877eb269044411e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/DefaultTextMaterialEmissive.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_AnchorIcon.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_AnchorIcon.uasset new file mode 100644 index 0000000000000000000000000000000000000000..20c85682a02ce732ff5c70ffc3a97627bab79a9a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_AnchorIcon.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_HelpInfoBackground.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_HelpInfoBackground.uasset new file mode 100644 index 0000000000000000000000000000000000000000..30cdaa85be2ed68bb3844827dc2afababd79e657 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_HelpInfoBackground.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_LaserBeam.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_LaserBeam.uasset new file mode 100644 index 0000000000000000000000000000000000000000..693d55415b41fd0ca10923c9ab1b6797d318579d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_LaserBeam.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_MenuBackground.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_MenuBackground.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0394b799c3759dd7a0a778642eba32df873bcef3 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_MenuBackground.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_MenuItemSurface.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_MenuItemSurface.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3d40a5132daebd9c28288f862c4585a91f677e2c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MI_MenuItemSurface.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MPC_AnchorStates.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MPC_AnchorStates.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3ecce164168eb9b2c5f89ee236acc6b6a67c698d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/MPC_AnchorStates.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/M_ColoringMaterial.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/M_ColoringMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e4161043ed44d4128785d142c04772299306ef60 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/M_ColoringMaterial.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/M_UIIcon.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/M_UIIcon.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9524064b37e8ad8eb3eb93141de85a00b7dcc703 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/MaterialLibrary/M_UIIcon.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/Laser_Mesh.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/Laser_Mesh.uasset new file mode 100644 index 0000000000000000000000000000000000000000..71d991627fce2cb2271ba250e4e922ef88b35462 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/Laser_Mesh.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_Anchor.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_Anchor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0b67c728b7e7b5ac181f1449e4c1be969510294b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_Anchor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_ForwardAxis.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_ForwardAxis.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d9f6143526f395f21cf0d30ca0c04d9a9204a7fd Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_ForwardAxis.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_RightAxis.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_RightAxis.uasset new file mode 100644 index 0000000000000000000000000000000000000000..33607646cbeb53dbbf83bf37f23b53f8ee08a019 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_RightAxis.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_UpAxis.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_UpAxis.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a747d5280c1c4e9b92628d0cbf3c8665fa198c21 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Meshes/SM_UpAxis.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Minimize.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Minimize.uasset new file mode 100644 index 0000000000000000000000000000000000000000..40cb28457dcd65cd374e4019f7bb49c1a7ebc333 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Minimize.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Move.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Move.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e7ac4868eb15b5c71ff0b8679bf863f5695ba6fe Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Move.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/NoStorageSD.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/NoStorageSD.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5842b76ef70de02756a05e42de996a952d2a6ef4 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/NoStorageSD.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Roomscale.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Roomscale.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b8aa31d50cfeb3143926d9780e810de8f7f404fd Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Roomscale.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/StorageSD.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/StorageSD.uasset new file mode 100644 index 0000000000000000000000000000000000000000..64ebe64adf8d72c6dcc692574922e7a028ee8cff Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/StorageSD.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/ToTop.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/ToTop.uasset new file mode 100644 index 0000000000000000000000000000000000000000..2616cfc91c3a49ef55e217883867d4747b811a75 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/ToTop.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Trash.uasset b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Trash.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5dd9954e9cb02f48a33d5aa9fb283b840ab72702 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/SpatialAnchorsSample/Textures/Trash.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/MH_Ragdoll_AnimBP.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/MH_Ragdoll_AnimBP.uasset new file mode 100644 index 0000000000000000000000000000000000000000..8febe3767457f04d3a2d30c4c062c1fd92f52968 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/MH_Ragdoll_AnimBP.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/PhysicalBehavior_AnimBP_Data.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/PhysicalBehavior_AnimBP_Data.uasset new file mode 100644 index 0000000000000000000000000000000000000000..408a5e68fe90ef37b7172b71b29730d839cad1bd Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/PhysicalBehavior_AnimBP_Data.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/PhysicalBehavior_BlueprintLibrary.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/PhysicalBehavior_BlueprintLibrary.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b97b91d6b433c895d42228ac987ac8dab2b72f16 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/PhysicalBehavior_BlueprintLibrary.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/Physical_Behavior_ActorComponent.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/Physical_Behavior_ActorComponent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7a1783f4385db7a55ae002f9728e1531e13ab997 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/Physical_Behavior_ActorComponent.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/RollDownSlope_ActorComponent.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/RollDownSlope_ActorComponent.uasset new file mode 100644 index 0000000000000000000000000000000000000000..894cb046c4c24d89d9ab068dfe1c2490b718d39f Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/RollDownSlope_ActorComponent.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/m_med_nrw_ragdoll.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/m_med_nrw_ragdoll.uasset new file mode 100644 index 0000000000000000000000000000000000000000..81f54e47ed2292d66a8717bb21e78c3ae27d60b5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/AnimBP/m_med_nrw_ragdoll.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Backward_Get_Up.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Backward_Get_Up.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d2b98bef7373f5e1ea7cfaa9191624d87007ef5d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Backward_Get_Up.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/CR_Mannequin_BasicFootIK.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/CR_Mannequin_BasicFootIK.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3b39cf60d1b2f8850916e44eec67d54cfe66cf84 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/CR_Mannequin_BasicFootIK.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Fetal.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Fetal.uasset new file mode 100644 index 0000000000000000000000000000000000000000..07cadcd986a341121d911d6df338b93b8b6d9b13 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Fetal.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Forward_Get_Up.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Forward_Get_Up.uasset new file mode 100644 index 0000000000000000000000000000000000000000..96382885df52822bd993b12d8a632f407ecc20dc Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Forward_Get_Up.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/BS_MM_WalkRun.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/BS_MM_WalkRun.uasset new file mode 100644 index 0000000000000000000000000000000000000000..8241736c356cf3f4d68208f43591f34aa7c99002 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/BS_MM_WalkRun.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Fall_Loop.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Fall_Loop.uasset new file mode 100644 index 0000000000000000000000000000000000000000..00ee3c6c0473f769a7ea3ea635868a696f1864dd Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Fall_Loop.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Idle.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Idle.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c4f8f77d5562e12f3e2511bd8688cdde34741b6c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Idle.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Jump.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Jump.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4b57a5cfce7e1785b7e27962ca705c0b98d670c1 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Jump.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Land.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Land.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9d1780776cfb8065bc1ab9ea232fbcaaef2d5779 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Land.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Run_Fwd.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Run_Fwd.uasset new file mode 100644 index 0000000000000000000000000000000000000000..719939630e6f841fff043fceb4ae0ad7992a6771 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Run_Fwd.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Walk_Fwd.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Walk_Fwd.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f34d005a1b4a17b410592d0d3b50bee5a4a68a40 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Walk_Fwd.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Walk_InPlace.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Walk_InPlace.uasset new file mode 100644 index 0000000000000000000000000000000000000000..29615dfdcdb8b53bb1f2460b6d4e5340397668cf Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Locomotion/MM_Walk_InPlace.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/MirrorDataTable.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/MirrorDataTable.uasset new file mode 100644 index 0000000000000000000000000000000000000000..95122f4b21e50ff049ec26279565263ec3e95dd3 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/MirrorDataTable.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_1.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_1.uasset new file mode 100644 index 0000000000000000000000000000000000000000..aaf5fb07664ac945785c259ec623afaa30856aa4 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_1.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_2.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_2.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0fc26b7445c1a3dfdd391aa634e34cc5931dfc02 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_2.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_3.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_3.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a8cdff057585665249351c091eafdb26d2dedecc Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_3.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_4.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_4.uasset new file mode 100644 index 0000000000000000000000000000000000000000..86b36cbc05b0b1d2db06516c4721e5ee595daea8 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Animation/Roll_4.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/M_MirrorRT.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/M_MirrorRT.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d99a0576f32c763614172b4aeabc355792bf71c5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/M_MirrorRT.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_Floor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_Floor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..96b27661e57cbc13df93dd782a002861c779716f Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_Floor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_Laser.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_Laser.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3357939b5d1d7b0b65567adc24617d661a2ebc45 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_Laser.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorBorder.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorBorder.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7917020c384b2f340dfef22b95cbe16f0687e37e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorBorder.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorGlass.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorGlass.uasset new file mode 100644 index 0000000000000000000000000000000000000000..05d32221f6adf6289d691c3f8f2bb58832638aa3 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorGlass.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorGlass1.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorGlass1.uasset new file mode 100644 index 0000000000000000000000000000000000000000..709e434d50ca2ab5b9ec0efb889648e3ea7cb4ee Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_MirrorGlass1.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_TextUnlit.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_TextUnlit.uasset new file mode 100644 index 0000000000000000000000000000000000000000..abe34eaed5bb960498e4686e607e1d4fa9a7437e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/M_TextUnlit.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Body.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Body.uasset new file mode 100644 index 0000000000000000000000000000000000000000..730221397fa7b87d4b1516708209972644688b54 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Body.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Clothes.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Clothes.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6335ce8fb05e64639c4189207f97dfe44efda881 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Clothes.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Eye.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Eye.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ced5538ec324ce89c6f80e221bd3fbfcfeaf948e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Eye.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Head.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Head.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7747c7622a9a4ff845ce654b2942b78748710794 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Head.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Teeth.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Teeth.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3768cda4ba2b81dfa743caf02c61ba9e388db694 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Proteus/M_Proteus_Teeth.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Widget3DAlwaysOnTop.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Widget3DAlwaysOnTop.uasset new file mode 100644 index 0000000000000000000000000000000000000000..24dfbfa718d3f053d02a8b80a85be43f14da1772 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Materials/Widget3DAlwaysOnTop.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/BeamMesh.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/BeamMesh.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5eab99424e3f2f447a5d97b5d92fcef3ad0be7cf Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/BeamMesh.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Mirror.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Mirror.uasset new file mode 100644 index 0000000000000000000000000000000000000000..64ee87861dc97c380dc0209059098229c570d19c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Mirror.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/PA_Proteus.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/PA_Proteus.uasset new file mode 100644 index 0000000000000000000000000000000000000000..00970dad5dd6b4e762017d2c1a34fd789490fe98 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/PA_Proteus.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/SK_Proteus.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/SK_Proteus.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7be9496f80d509245fd4866eb018fe7c9dc78031 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/SK_Proteus.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/S_Proteus.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/S_Proteus.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e7473c8bf7abbf51b49c6b28e5a07c8a82e5a810 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Models/Proteus/S_Proteus.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/face_albedo_diffuse.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/face_albedo_diffuse.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e7a72d505d57b885587b08ebc58fe0546d59d3cd Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/face_albedo_diffuse.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/face_albedo_grey.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/face_albedo_grey.uasset new file mode 100644 index 0000000000000000000000000000000000000000..eb172b275271bae431af86dcbd624b0121fa811e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/face_albedo_grey.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/generic_avatar_eye_grey.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/generic_avatar_eye_grey.uasset new file mode 100644 index 0000000000000000000000000000000000000000..791420e99d3c0d34687931cb438ae95e64f7be32 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/generic_avatar_eye_grey.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/generic_mouth_stylized_grey.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/generic_mouth_stylized_grey.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a7700dde442b8e0a0dbf325e8655713a6e98f829 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Assets/Textures/Proteus/generic_mouth_stylized_grey.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Avatar.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Avatar.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b2d2f989b49689421cde1ab525012867d309ce1f Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Avatar.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BPPickUpArtifact.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BPPickUpArtifact.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ea9cf3952ae4081bab87a08d932f70b75cf83852 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BPPickUpArtifact.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_FirstPersonGameMode.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_FirstPersonGameMode.uasset new file mode 100644 index 0000000000000000000000000000000000000000..198be61893f4a7f2261dbc324ac73b8b39d2cf7e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_FirstPersonGameMode.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_MotionController.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_MotionController.uasset new file mode 100644 index 0000000000000000000000000000000000000000..886679d517b31f449e22ec2ddf48f3bf071f792a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_MotionController.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_RiggedHand.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_RiggedHand.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a5d7e29add2c572426a6980cb0f0d4db09722f84 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_RiggedHand.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_ThirdPersonCharacter.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_ThirdPersonCharacter.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5ceb5bb58a8be61424c815bdacee810484cd45cb Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_ThirdPersonCharacter.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_VRPawn.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_VRPawn.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9f785b46ef260f39e589a473709701be217c1cef Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BP_VRPawn.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BlueprintUtils.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BlueprintUtils.uasset new file mode 100644 index 0000000000000000000000000000000000000000..92128c6f3b3a9212be139b9479c5464edcd4fb05 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/BlueprintUtils.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/GripEnum.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/GripEnum.uasset new file mode 100644 index 0000000000000000000000000000000000000000..954ee28221bcbec559e1289b1aa52ad95d61f86a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/GripEnum.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Hand_BP.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Hand_BP.uasset new file mode 100644 index 0000000000000000000000000000000000000000..72419e83ccdc2c3267279c242c06bd3b84218b5e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Hand_BP.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HandsVR.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HandsVR.uasset new file mode 100644 index 0000000000000000000000000000000000000000..78dfdd31cbe34cd40b7e28c12fbfbe2170ad6e70 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HandsVR.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HeadActor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HeadActor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6b5d9bb9800cabd9d8061bf33a01fdc05ea59ce5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HeadActor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HoverPlatform.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HoverPlatform.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3324756f4e2c12c469ec5f809e754aacc3576720 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/HoverPlatform.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/IPawnVR.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/IPawnVR.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a5b0bfdb9f03fd0bffebfd9368e299a59ce936f5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/IPawnVR.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/MirrorActor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/MirrorActor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1d20720bfb9aeeedd7315b6ce128669e34739ec5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/MirrorActor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Misc/GameModeVR.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Misc/GameModeVR.uasset new file mode 100644 index 0000000000000000000000000000000000000000..68dd96a9c02e51f4ab149da22f12cab4675cc0fa Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Misc/GameModeVR.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Misc/PawnMacros.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Misc/PawnMacros.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1247f491b2091a4ee9e93be3f89a74e8402722fa Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Misc/PawnMacros.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/PickupActorInterface.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/PickupActorInterface.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a76f2c474311424806492f221fb33a215db14999 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/PickupActorInterface.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/PlayerSave.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/PlayerSave.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f5627b5b2c13e89b440748563822748c347065e8 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/PlayerSave.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/RoomAnchor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/RoomAnchor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f5fe546e6c0c93fa9812d8ea26e3f31ea8e6f53d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/RoomAnchor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/VRCharacter.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/VRCharacter.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5c032270cc89276880f054305bf23d90ee1e8675 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/VRCharacter.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/VRHand.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/VRHand.uasset new file mode 100644 index 0000000000000000000000000000000000000000..279ac906e92da1d4963ae20c2ecad3ee8a86463c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/VRHand.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/ListViewEntry.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/ListViewEntry.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6285752a122424723f3be119790c515db3894c8d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/ListViewEntry.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/ListViewWidget.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/ListViewWidget.uasset new file mode 100644 index 0000000000000000000000000000000000000000..cabdffdcf0ddd524091ef6d9d2515ac60a4c12ec Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/ListViewWidget.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/SData.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/SData.uasset new file mode 100644 index 0000000000000000000000000000000000000000..26a99c77d577a3b74e1b84bb5c1491aac94a112b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/SData.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/UData.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/UData.uasset new file mode 100644 index 0000000000000000000000000000000000000000..33c0623ec216809c21fe85a2ca95c28e5079339a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/UData.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/WidgetActor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/WidgetActor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b9eda1029911946852379f06a6aedced6ea8d73e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Blueprints/Widget/WidgetActor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/LeftControllerStick.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/LeftControllerStick.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e91eee4b7d477f2fcb7197263169d7531100b0eb Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/LeftControllerStick.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/RightControllerStick.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/RightControllerStick.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3c4faf7b1fbd97152609c18bef665fc18054f999 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/RightControllerStick.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_button01_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_button01_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d9e5fff57034b8039b8430cf31f5662c683677e8 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_button01_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_button02_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_button02_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..54ea31a649a18a44e4c8d43b03dec169c284a737 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_button02_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_grip_button_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_grip_button_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..aaeab8c6161719e98637782040a281b0db7d3208 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_grip_button_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_grip_pull.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_grip_pull.uasset new file mode 100644 index 0000000000000000000000000000000000000000..66e0cca15d4bfe7205784233f6bdc636ec66d62e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_grip_pull.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_E_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_E_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..44ef2a59ad803983a0e92c1ef1ec436a1e7689d5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_E_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_NE_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_NE_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..273241821049b2eb1ce2b79a0b5c72fd27e2c9d7 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_NE_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_NW_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_NW_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a6a9b902d8a88adabad5d31aa6f6c8fd933f295f Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_NW_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_N_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_N_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1b49aa6112f3c5bf4792db49ac0706ca02a29934 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_N_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_SE_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_SE_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1a0294f542d70ffaf4ed97534a5c6ecb2f976efa Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_SE_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_SW_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_SW_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0aa026b93ea2c5584cfa4576c135bd6fce22aed9 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_SW_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_S_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_S_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e594e9a3e6d7af62cbae25e55c551d2f76be89e2 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_S_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_W_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_W_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9bf1fd93bb38c43d0742b7403cce3bfd84c5c101 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_joy_W_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_neutral_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_neutral_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3f53a791ed39d5c79d891d62bad67315af79f3e6 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_neutral_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_press_B1.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_press_B1.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b210c961221ad9676e89d5fe1a3da3044c1150f9 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_press_B1.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_press_B2.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_press_B2.uasset new file mode 100644 index 0000000000000000000000000000000000000000..22b97427aa92b353f32c41d57beb8f0a44382ee5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_press_B2.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_trigger_pull.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_trigger_pull.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b73f3b5f1d01cd560c054610d7301e5fc1b6e4b8 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_trigger_pull.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_trigger_pull_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_trigger_pull_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d60f2c72bb246f6fd1d57a4ff5a8325c8078e08b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/l_controller_trigger_pull_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_button01_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_button01_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7bd7f9f74357324112e54028a08b90483f80be0c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_button01_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_button02_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_button02_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..197f79c31977f6a215e966134e2c082b08b408e8 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_button02_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_grip_button_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_grip_button_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1f9bbbccb2b69349641e99f829bbc32a2481e787 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_grip_button_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_grip_pull.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_grip_pull.uasset new file mode 100644 index 0000000000000000000000000000000000000000..928b4f1150a3887dc4c825f0277e3fe70b7654b7 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_grip_pull.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_E_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_E_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9989e556a805c8e5d5412df162aef93850a6e25a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_E_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_NE_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_NE_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..2cda7a32cd2c5713150560c4276f4d40d885d2b9 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_NE_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_NW_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_NW_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..eea7a542f2255ad55299499688fdd5b9c0ef515e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_NW_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_N_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_N_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..629217a368af4d81019dee69ed7f44833afa9309 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_N_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_SE_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_SE_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..dd7b8fa81fc579d9643908b9bb0eeab27f8ef304 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_SE_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_SW_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_SW_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ef5ec4cd3c4fa89f5c8d9d57da635e1e29f3fef8 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_SW_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_S_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_S_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0e65ac7638ecc2978822db497d8b7bbb311e41ee Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_S_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_W_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_W_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..2526fbbcde4f607b5443c65446161c9f3aee88ad Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_joy_W_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_neutral_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_neutral_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..39e5375289038bc7ff2a25a719db9b0d4b9de1a5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_neutral_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_press_B1.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_press_B1.uasset new file mode 100644 index 0000000000000000000000000000000000000000..794b99d0e46d88159e3760a3bf27673bc541fd2b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_press_B1.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_press_B2.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_press_B2.uasset new file mode 100644 index 0000000000000000000000000000000000000000..177aadabf1a4a7b0d4d1f807a7900607bb0bca53 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_press_B2.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_trigger_pull.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_trigger_pull.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e34fee6236c288c5e34aefebc272c5d303bc0433 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_trigger_pull.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_trigger_pull_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_trigger_pull_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c9a074df60818089969aa35568db1db2abf79294 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Animations/r_controller_trigger_pull_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/LeftController.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/LeftController.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f3c1bdf0c66b86e892b39785bb6a98e8da373d93 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/LeftController.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/LeftControllerAnimBP.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/LeftControllerAnimBP.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a18eba0fe3f398c46c17fb32e63343afab0080b7 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/LeftControllerAnimBP.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4a046ef3f53697c78c9f02a03eef1a213c597c52 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel_PhysicsAsset.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel_PhysicsAsset.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7776a8fd162657722aba32501973573a4b0ab21d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel_PhysicsAsset.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a5f7caf11dd807297a38b3ee52cc801661557e71 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/left_touch_controller_model_skel_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel.uasset new file mode 100644 index 0000000000000000000000000000000000000000..49add00852aee6ee7fae94f554297437c518e132 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel_PhysicsAsset.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel_PhysicsAsset.uasset new file mode 100644 index 0000000000000000000000000000000000000000..352783ece6a8db850dfc1b7ba5ff6a8580a83f93 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel_PhysicsAsset.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..55683bcd08481b24560b81cb0df2ee0e9492ffea Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/right_touch_controller_model_skel_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_albedo.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_albedo.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5079c4f5093c2f37685e464483c619d43581462c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_albedo.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_controlmap.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_controlmap.uasset new file mode 100644 index 0000000000000000000000000000000000000000..149c9d9f231d21fd903c122c0d2569fa3c5032a6 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_controlmap.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_mat.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_mat.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b070dee7d3d8808d63773d3021bccc792345355e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/Models/touchController_mat.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/RightController.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/RightController.uasset new file mode 100644 index 0000000000000000000000000000000000000000..2118d045b83e2aa2179fa20be0d8140ef86179a6 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/RightController.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/RightControllerAnimBP.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/RightControllerAnimBP.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4f777dcbd72f348d5c109bacad4728f101e3503f Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Controllers/RightControllerAnimBP.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Materials/M_HandTracking.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Materials/M_HandTracking.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d5a0cf5aff725bb12aa129858eef2ed981fdadb4 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Materials/M_HandTracking.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Materials/M_HandTracking_Inst.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Materials/M_HandTracking_Inst.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b1114976b2f4900bbfb85c5783ff014dfab622b9 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Materials/M_HandTracking_Inst.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/LeftHand_new_1105CX.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/LeftHand_new_1105CX.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b39adc395409ec2a7f25ba781384704ba26e0df1 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/LeftHand_new_1105CX.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/LeftHand_new_1105CX_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/LeftHand_new_1105CX_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1de6b7930dd0454385dd81173a2c620663f2d271 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/LeftHand_new_1105CX_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/RightHand_new_1105CX.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/RightHand_new_1105CX.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b1405bf29a9b3c1993c46249c4e942d794e30b7c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/RightHand_new_1105CX.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/RightHand_new_1105CX_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/RightHand_new_1105CX_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ef17a260c63636aee07713abe553dbb4ce7eb946 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/HandTrackingModel/Model/RightHand_new_1105CX_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_cap_touch_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_cap_touch_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..563770ee6728c17a9733f5ae3499209295b91f4c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_cap_touch_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_cap_touch_thumb_mask_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_cap_touch_thumb_mask_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1e337635a4db37761be1e4718e1faf50629fc5ba Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_cap_touch_thumb_mask_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_default_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_default_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0704a3804b7641b45bbdcf2a81b024d0f50db4c0 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_default_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_fist_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_fist_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..bdd00e7a0e5c8037a6e4981784a43d94e60203c7 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/l_hand_fist_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_cap_touch_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_cap_touch_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1bd96394418c34aee7460d227dd86f6c52992153 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_cap_touch_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_cap_touch_thumb_mask_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_cap_touch_thumb_mask_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..47abfbe2be5a11a45b2186563fffee54ee897b3f Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_cap_touch_thumb_mask_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_default_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_default_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9b9b2926347493c892417d8dbe9bd12edeb3fc84 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_default_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_fist_anim.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_fist_anim.uasset new file mode 100644 index 0000000000000000000000000000000000000000..8b3590902b069cbbbde7c8f4e2695eddc241a5f3 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Animations/r_hand_fist_anim.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandAnimCommon.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandAnimCommon.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f1ad5abbcf45416a2929e53220d834df72daa4ef Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandAnimCommon.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandAnimInterface.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandAnimInterface.uasset new file mode 100644 index 0000000000000000000000000000000000000000..46fda5b3bc13dbf8989ccb9d38aac7c4b1bc898d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandAnimInterface.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandPoseEnum.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandPoseEnum.uasset new file mode 100644 index 0000000000000000000000000000000000000000..99db6c7a941e367b066e66c67a2a37021bea7dce Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/HandPoseEnum.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/LeftHand.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/LeftHand.uasset new file mode 100644 index 0000000000000000000000000000000000000000..301a0195b92dc506bf3277041d575bc455e45fc2 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/LeftHand.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/LeftHandAnimBP.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/LeftHandAnimBP.uasset new file mode 100644 index 0000000000000000000000000000000000000000..16f15e63e02a2d69d2bac9728e65e893d6f48d13 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/LeftHandAnimBP.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/M_Glove_Red.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/M_Glove_Red.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ea625190b9cab1f186ec0581cc87fccd8c1986ca Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/M_Glove_Red.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/HandMat.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/HandMat.uasset new file mode 100644 index 0000000000000000000000000000000000000000..dc86ce37a9e6d42b68fbf5905a3d134a9fdb974b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/HandMat.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_L.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_L.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9947dd2eae1a2d4c1cdb407f40ac9a74b7c6dc8f Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_L.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_L_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_L_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9a46ad3af8cb1be08381378e00e520be83bb222d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_L_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_R.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_R.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6dcf34244466917fed217e88ccc6137d46cdbb15 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_R.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_R_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_R_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..cc958ad163f800ba6006139ea2155a370ae2b6d2 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/OculusHand_R_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4b93130163a4b5ea89c623ba94b745c36a62d572 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres_PhysicsAsset.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres_PhysicsAsset.uasset new file mode 100644 index 0000000000000000000000000000000000000000..90841607d369ec277745446725d278859e4970c3 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres_PhysicsAsset.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..92ac38f0fce34ca7929d3dce44f70d9bc4b0cf43 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/l_hand_skeletal_lowres_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4e32f3a10448cb9e8e1ecc36e45d25dfb4a72e6c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres_PhysicsAsset.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres_PhysicsAsset.uasset new file mode 100644 index 0000000000000000000000000000000000000000..103e93fed10284a09ff430e4954b7a16c0f63417 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres_PhysicsAsset.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c37f98fd35a6a6380ffe4771603f1f05c9243554 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/Models/r_hand_skeletal_lowres_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/RightHand.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/RightHand.uasset new file mode 100644 index 0000000000000000000000000000000000000000..171598a1652f69b68a8f666944dfcb21dfd79858 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/RightHand.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/RightHandAnimBP.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/RightHandAnimBP.uasset new file mode 100644 index 0000000000000000000000000000000000000000..bf65a23a9eb7fe2d21b775276f90b8dc4b5d8e97 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/RightHandAnimBP.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SK_Glove_Left.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SK_Glove_Left.uasset new file mode 100644 index 0000000000000000000000000000000000000000..bd4f3714f03ce110cf0d18ec015263fd368243db Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SK_Glove_Left.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SK_Glove_Right.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SK_Glove_Right.uasset new file mode 100644 index 0000000000000000000000000000000000000000..26087fba368c5b5006a3d2e856c4054f1751c81a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SK_Glove_Right.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/ANIM_Glove_Left.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/ANIM_Glove_Left.uasset new file mode 100644 index 0000000000000000000000000000000000000000..766d8a70cade9de2ceb7c2af536439ce2d01e26c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/ANIM_Glove_Left.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/ANIM_Glove_Right.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/ANIM_Glove_Right.uasset new file mode 100644 index 0000000000000000000000000000000000000000..78f279655dd93cdbd1bcea818cdf6c92f6aec252 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/ANIM_Glove_Right.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Bow.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Bow.uasset new file mode 100644 index 0000000000000000000000000000000000000000..38bcfb2d915f0b125debcf5e61c388ea51c96bc9 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Bow.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Controller.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Controller.uasset new file mode 100644 index 0000000000000000000000000000000000000000..aa9dcb8d07a842f108fb7e5e59c37424bf97f4bd Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Controller.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Cube.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Cube.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3a110e76a1fc86a14c098d4d83ae8c9718d2f29a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Cube.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Open.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Open.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1ff8ebd89f461261f984530816d7fb62f078ec7a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Open.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Squishy.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Squishy.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9b0666ff91671d863327034ce4e648be6552b8f7 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Left_Squishy.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Bow.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Bow.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3200b21ab2db5aa6eecb77d47374cc969dd4d902 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Bow.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Controller.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Controller.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1dfa4ecf9acb1c3c525d11f347b4ae502b613297 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Controller.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Cube.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Cube.uasset new file mode 100644 index 0000000000000000000000000000000000000000..eb669e7b9779824153f094a4f8527b35162f9b49 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Cube.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Open.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Open.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4a2dbbd887669016bf7465715c57eb9ef9f7d60d Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Open.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Squishy.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Squishy.uasset new file mode 100644 index 0000000000000000000000000000000000000000..81fda54dd493aebc5833009ad3b1efa3e0de0438 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/SteamVR_Animations/SteamVR_Hand_Right_Squishy.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/T_Glove_Red.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/T_Glove_Red.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c5c371dd11faa983255ddec6ec7361a2a389a223 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/T_Glove_Red.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/T_Glove_Red_N.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/T_Glove_Red_N.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ac8580fdc0aa3f326ceaab685ab6b6fff0c127a3 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/T_Glove_Red_N.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/vr_glove_left_model_slim_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/vr_glove_left_model_slim_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e64eda2186b4f27e1605a4bcc4bc08b66f9d7290 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/vr_glove_left_model_slim_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/vr_glove_right_model_slim_Skeleton.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/vr_glove_right_model_slim_Skeleton.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a11156ec172fa69c08a4e8969b48a543b2e841fc Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Hands/vr_glove_right_model_slim_Skeleton.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Maps/EmptyStart.umap b/Plugins/CyberArchWarehouse/Content/xrPerson/Maps/EmptyStart.umap new file mode 100644 index 0000000000000000000000000000000000000000..770fd235d198897781571a01cb3469dbfc8571d7 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Maps/EmptyStart.umap differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/EnglishMat.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/EnglishMat.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6b227bd869dda60c0c3e8fff25add1aa94fae331 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/EnglishMat.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MF_OccludedPixels.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MF_OccludedPixels.uasset new file mode 100644 index 0000000000000000000000000000000000000000..20908a8fd99fab843e5cbf59bdeea49591c7db50 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MF_OccludedPixels.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MI_ChaperoneOutline.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MI_ChaperoneOutline.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f712dbc6236861314f71d585c61ce2be881ead76 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MI_ChaperoneOutline.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MI_TeleportCylinderPreview.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MI_TeleportCylinderPreview.uasset new file mode 100644 index 0000000000000000000000000000000000000000..20e19a4bd07a5525441aaaff5aa236c44555bf39 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/MI_TeleportCylinderPreview.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_ArcEndpoint.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_ArcEndpoint.uasset new file mode 100644 index 0000000000000000000000000000000000000000..371145fcbd8a3ed7055356ee67c0c67dd795a22e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_ArcEndpoint.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_SplineArcMat.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_SplineArcMat.uasset new file mode 100644 index 0000000000000000000000000000000000000000..cdc876f21157743299d49e4a7f2058bb84a7d03b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_SplineArcMat.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_TeleportPreviews.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_TeleportPreviews.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5635c82b362f874f55092183fd95791462aa58a9 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/M_TeleportPreviews.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/SpanishMat.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/SpanishMat.uasset new file mode 100644 index 0000000000000000000000000000000000000000..48717065288a6efe31d20fec9e3582e2c9a22852 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/SpanishMat.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/TeleportMCP.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/TeleportMCP.uasset new file mode 100644 index 0000000000000000000000000000000000000000..32a4f05f7194e6daa6a5c74f0aee2936027e81e2 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/TeleportMCP.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/english.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/english.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a8090bf966ddeba42d491d3667ed0f1f69b33e88 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/english.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/spanish.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/spanish.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4118797526ec8fd6eb28ea7806eea91b52efbe50 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Materials/spanish.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/1x1_cube.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/1x1_cube.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0ba427d8bb1707c26afc224cb7e528737c721a0b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/1x1_cube.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/BeaconDirection.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/BeaconDirection.uasset new file mode 100644 index 0000000000000000000000000000000000000000..dd0f34074d6c94db730e6193b151f5e1d453dcf7 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/BeaconDirection.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/BeamMesh.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/BeamMesh.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f5f6eef47ac8b8d88ebf8dca3b1b3ebcb3a06237 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/BeamMesh.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/SM_FatCylinder.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/SM_FatCylinder.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c684c5443231605c95ce5dd08900fe013134ee97 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/Meshes/SM_FatCylinder.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/CanvasRenderTarget2D.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/CanvasRenderTarget2D.uasset new file mode 100644 index 0000000000000000000000000000000000000000..8d09abaffd6ccb2fd38adb074b444ce90a2c9690 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/CanvasRenderTarget2D.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/DariyaStream.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/DariyaStream.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4c1fee30a3542adbe8522c6477abbb34f8d6276a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/DariyaStream.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/MouseOverActor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/MouseOverActor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..6fbb2dbbde37e67e7922893abb813a007955603e Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/MouseOverActor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewFileMediaSource.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewFileMediaSource.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4f5df007948dffc481e336228983251d58b17ecd Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewFileMediaSource.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewMediaPlayer.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewMediaPlayer.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0fa3e3968524ad0fc69e0d981dfdd999e74653ac Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewMediaPlayer.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewMediaTexture.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewMediaTexture.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f36d82bacc5154b0acf5299b5e4c6c5f42cf374c Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewMediaTexture.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewStreamMediaSource.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewStreamMediaSource.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d2671f19780a54fe4665ce8ceab948992b5b4b53 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/NewStreamMediaSource.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoActor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoActor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..106249eb3d5f6da26cf1432d8757be0629713cf3 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoActor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoAtten.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoAtten.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9bea6ace7fbcb42981e0ff2ace562cedd7b9008b Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoAtten.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoMaterial.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..08768b3c8cc46cdafd13c80cb8bbc057481b4d72 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoMaterial.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoWidget.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoWidget.uasset new file mode 100644 index 0000000000000000000000000000000000000000..32db670ac13ca48018baa35a9bf0d96c2b916398 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/VideoWidget.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebActor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebActor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7107f26e8f88c4113a7bb5cbeb61e26fb2e65bc5 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebActor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebBrowser.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebBrowser.uasset new file mode 100644 index 0000000000000000000000000000000000000000..bd72a82520f174a875940450709b9957fbba5aa1 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebBrowser.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebBrowserActor.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebBrowserActor.uasset new file mode 100644 index 0000000000000000000000000000000000000000..dda9dfcb45ce4a4065a7d5b2327a47b256f1129a Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/WebBrowserActor.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/sRGBtoRGB.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/sRGBtoRGB.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d4ad344c1363a09b5d601592de95f775a9803da6 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/sRGBtoRGB.uasset differ diff --git a/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/wallpaperbetter.uasset b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/wallpaperbetter.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7764e4a9f3ac3f5ddd0690cb7db9d4989b6444a0 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Content/xrPerson/UI/webbrowser/wallpaperbetter.uasset differ diff --git a/Plugins/CyberArchWarehouse/CyberArchWarehouse.uplugin b/Plugins/CyberArchWarehouse/CyberArchWarehouse.uplugin new file mode 100644 index 0000000000000000000000000000000000000000..63eaa3cae98b9928a8811b93c4b11fa3b1472860 --- /dev/null +++ b/Plugins/CyberArchWarehouse/CyberArchWarehouse.uplugin @@ -0,0 +1,21 @@ +{ + "FileVersion": 3, + + "FriendlyName": "CyberArchWarehouse", + "Version": 1, + "VersionName": "1.0", + "CreatedBy": "Neil Smith, Haoyang Guo", + "CreatedByURL": "", + "EngineVersion": "5.03", + "Description": "CyberArchWarehouse for UE5", + "Category": "", + "CanContainContent": true, + "EnabledByDefault": true, + + "Modules": [ + { + "Name": "CyberArchWarehouse", + "Type": "Runtime" + } + ] +} \ No newline at end of file diff --git a/Plugins/CyberArchWarehouse/Resources/Icon128.png b/Plugins/CyberArchWarehouse/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..3033ae0eddc8c201daf7a3ffffe1ff15e776a7f3 Binary files /dev/null and b/Plugins/CyberArchWarehouse/Resources/Icon128.png differ diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/CyberArchWarehouse.Build.cs b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/CyberArchWarehouse.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..fb25b3ece3a3d6639f1f3065078710afd8c6934b --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/CyberArchWarehouse.Build.cs @@ -0,0 +1,28 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. +using UnrealBuildTool; +using System.IO; + +public class CyberArchWarehouse : ModuleRules +{ + public CyberArchWarehouse(ReadOnlyTargetRules Target) : base(Target) + { + PrivateIncludePaths.AddRange(new string[] { "CyberArchWarehouse/Private" }); + PrivatePCHHeaderFile = "Private/CyberArchWarehousePrivatePCH.h"; + + PublicDependencyModuleNames.AddRange( + new string[] { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "RHI", + "RenderCore", + "UMG", + "Slate", + "SlateCore", + "ProceduralMeshComponent" + } + ); + } + +} diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/AssetViewerButton.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/AssetViewerButton.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1e863f9b434bf74b8db149023271157142ff879e --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/AssetViewerButton.cpp @@ -0,0 +1,113 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "AssetViewerButton.h" +#include "PickUpArtifact.h" +#include "Misc/FileHelper.h" +#include "Engine/World.h" +#include "Math/TransformNonVectorized.h" +#include "Math/Rotator.h" +#include "Math/Vector.h" +#include "Misc/CString.h" +//#include "GenericPlatform/MicrosoftPlatformString.h" + +void UAssetViewerButton::NativeConstruct() +{ + Super::NativeConstruct(); + + AssetButton->OnClicked.AddDynamic(this, &UAssetViewerButton::OnClick); +} + +void UAssetViewerButton::SetAssetName(FString AssetName) +{ + if (!AssetNameText) + return; + + AssetNameText->SetText(FText::FromString(AssetName)); + TargetAssetName = AssetName; +} + +void UAssetViewerButton::SetAssetPath(FString AssetPath) +{ + TargetAssetPath = AssetPath; +} + +void UAssetViewerButton::SetSpawner(AFileBrowser *FileBrowser) +{ + Spawner = FileBrowser; +} + +void UAssetViewerButton::OnClick() +{ + UE_LOG(LogTemp, Display, TEXT("Button for %s clicked"), *TargetAssetName); + + TArray<FString> FileContent; + FString MetaDataFilName = TargetAssetPath.Replace(TEXT(".uasset"), TEXT(".mesh")); + + FFileHelper::LoadFileToStringArray(FileContent, *MetaDataFilName); + + UE_LOG(LogTemp, Warning, TEXT("Your message %s"), *MetaDataFilName); + for (FString Line : FileContent) + { + UE_LOG(LogTemp, Warning, TEXT("Your message %s"), *Line); + } + + // Set Transform from metadata + FTransform SpawnTransform; + FString ScaleString; + TArray<FString> ScaleArray; + + + UStaticMeshComponent *SpawnerComponent = Cast<UStaticMeshComponent>(Spawner->GetDefaultSubobjectByName(TEXT("ImageSpawnerMesh"))); + // FVector SpawnerLocation = Spawner->GetActorLocation(); + FVector SpawnerLocation = SpawnerComponent->GetComponentLocation(); + SpawnerLocation = SpawnerLocation + FVector(0, 0, 100); + + UE_LOG(LogTemp, Warning, TEXT("Spawner Location %s"), *SpawnerLocation.ToString()); + + if (FileContent.Num() == 0) + { + SpawnTransform.SetRotation(FQuat(FRotator(0, 0, 0))); + SpawnTransform.SetLocation(SpawnerLocation); + } + else + { + FString LocationString = FileContent[1]; + FString RotationString = FileContent[2]; + ScaleString = FileContent[3]; + + TArray<FString> LocationArray; + LocationString.ParseIntoArray(LocationArray, TEXT(" "), true); + TArray<FString> RotationArray; + RotationString.ParseIntoArray(RotationArray, TEXT(" "), true); + + ScaleString.ParseIntoArray(ScaleArray, TEXT(" "), true); + + // SpawnTransform.SetLocation(FVector(FMicrosoftPlatformString::Atof(*LocationArray[0]), FMicrosoftPlatformString::Atof(*LocationArray[1]), FMicrosoftPlatformString::Atof(*LocationArray[2]))); + SpawnTransform.SetLocation(SpawnerLocation); + SpawnTransform.SetRotation(FRotator(FCString::Atof(*RotationArray[0]), FCString::Atof(*RotationArray[1]), FCString::Atof(*RotationArray[2])).Quaternion()); + } + + // spawn a APickUpArtifact + + APickUpArtifact *SpawnedPArtifact = GetWorld()->SpawnActorDeferred<APickUpArtifact>(PickUpArtifactClass, SpawnTransform); + // Set Scale from metadata + + SpawnedPArtifact->TargetArtifactName = TargetAssetPath; + // SpawnedPArtifact->TargetArtifactName = TargetAssetName; + + if (FileContent.Num() != 0) + { + int32 ScaleMultiplier = 10; + int32 Scale3DX = FCString::Atoi(*ScaleArray[0]) / ScaleMultiplier; + int32 Scale3DY = FCString::Atoi(*ScaleArray[1]) / ScaleMultiplier; + int32 Scale3DZ = FCString::Atoi(*ScaleArray[2]) / ScaleMultiplier; + SpawnedPArtifact->ArtifactMeshComponent->SetWorldScale3D(FVector(Scale3DX, Scale3DY, Scale3DZ)); + } + + // Pass in the reference FString + + // Complete the spawn + SpawnedPArtifact->FinishSpawning(SpawnTransform); + SpawnedPArtifact->SetArtifactComponentMesh(); + return; +} diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/AssetViewerGrid.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/AssetViewerGrid.cpp new file mode 100644 index 0000000000000000000000000000000000000000..053a0136c0cb7829b8e53fc7b71e76c80aad9975 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/AssetViewerGrid.cpp @@ -0,0 +1,46 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "AssetViewerGrid.h" +#include "Components/SizeBox.h" +#include "Blueprint/WidgetTree.h" + + +void UAssetViewerGrid::NativeConstruct() +{ + Super::NativeConstruct(); +} + +void UAssetViewerGrid::CreateAssetButton(int32 index, FString AssetName, FString AssetPath, AFileBrowser *FileBrowser) +{ + if (!AssetWrapBox) + return; + + UAssetViewerButton *NewButton = CreateWidget<UAssetViewerButton>(GetWorld(), AssetViewerButtonClass); + if (!NewButton) + return; + + NewButton->SetAssetName(AssetName); + NewButton->SetAssetPath(AssetPath); + NewButton->SetSpawner(FileBrowser); + + USizeBox *SizeBox = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass()); + + if (!SizeBox) + return; + + SizeBox->AddChild(NewButton); + + SizeBox->SetHeightOverride(145); + SizeBox->SetWidthOverride(400); + + AssetWrapBox->AddChildToWrapBox(SizeBox); +} + +void UAssetViewerGrid::ClearAssetButtons() +{ + if (!AssetWrapBox) + return; + + AssetWrapBox->ClearChildren(); +} + diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchCharacter.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchCharacter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..682fa90806c3cc8191722c0ff4da25fe8586384d --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchCharacter.cpp @@ -0,0 +1,52 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "CyberArchCharacter.h" + +// Sets default values +ACyberArchCharacter::ACyberArchCharacter() +{ + // Set this character to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + +} + +// Called when the game starts or when spawned +void ACyberArchCharacter::BeginPlay() +{ + Super::BeginPlay(); + +} + +// Called every frame +void ACyberArchCharacter::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + +// Called to bind functionality to input +void ACyberArchCharacter::SetupPlayerInputComponent(UInputComponent* PlayerInputComponent) +{ + Super::SetupPlayerInputComponent(PlayerInputComponent); + + // Bind the change avatar action. + PlayerInputComponent->BindAction<FInputSwitchAvatarDelegate>("MetaHuman1", IE_Released, this, &ACyberArchCharacter::ChangeAvatar, 1); + PlayerInputComponent->BindAction<FInputSwitchAvatarDelegate>("MetaHuman2", IE_Released, this, &ACyberArchCharacter::ChangeAvatar, 2); + PlayerInputComponent->BindAction<FInputSwitchAvatarDelegate>("MetaHuman3", IE_Released, this, &ACyberArchCharacter::ChangeAvatar, 3); + PlayerInputComponent->BindAction<FInputSwitchAvatarDelegate>("MetaHuman4", IE_Released, this, &ACyberArchCharacter::ChangeAvatar, 4); +} + +void ACyberArchCharacter::ChangeAvatar(int32 AvatarIndex) +{ + UE_LOG(LogTemp, Display, TEXT("Changing avatar to %d"), AvatarIndex); + FOutputDeviceNull ar; + const FString command = FString::Printf(TEXT("BP_ChangeAvatar %d"), AvatarIndex); + this->CallFunctionByNameWithArguments(*command, ar, NULL, true); +} + + + + + + diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchWarehouse.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchWarehouse.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b642a0a6b6eb9a2e9325f24df6b3e8ea2b069fcf --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchWarehouse.cpp @@ -0,0 +1,30 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "CyberArchWarehousePrivatePCH.h" + + +class FCyberArchWarehouse : public ICyberArchWarehouse +{ + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; + +IMPLEMENT_MODULE( FCyberArchWarehouse, CyberArchWarehouse ) + + + +void FCyberArchWarehouse::StartupModule() +{ + // This code will execute after your module is loaded into memory (but after global variables are initialized, of course.) +} + + +void FCyberArchWarehouse::ShutdownModule() +{ + // This function may be called during shutdown to clean up your module. For modules that support dynamic reloading, + // we call this function before unloading the module. +} + + + diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchWarehousePrivatePCH.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchWarehousePrivatePCH.h new file mode 100644 index 0000000000000000000000000000000000000000..e937819a129f799a77105aeb4b71d8101aed1ff0 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/CyberArchWarehousePrivatePCH.h @@ -0,0 +1,6 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#include "ICyberArchWarehouse.h" + +// You should place include statements to your module's private header files here. You only need to +// add includes for headers that are used in most of your module's source files though. diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/DirectoryViewerButton.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/DirectoryViewerButton.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b988f2132485c8ca2413c97f91cce00c7f842055 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/DirectoryViewerButton.cpp @@ -0,0 +1,34 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "DirectoryViewerButton.h" + +void UDirectoryViewerButton::NativeConstruct() +{ + + Super::NativeConstruct(); + + DirectoryButton->OnClicked.AddDynamic(this, &UDirectoryViewerButton::OnClick); +} + +void UDirectoryViewerButton::SetDirectoryName(FString DirectoryName) +{ + UE_LOG(LogTemp, Display, TEXT("set directory name executed")); + if (!FolderNameText) + return; + + FolderNameText->SetText(FText::FromString(DirectoryName)); + + TargetDirectoryName = DirectoryName; +} + +void UDirectoryViewerButton::SetFileBrowserActor(AFileBrowser *FileBrowserActor) +{ + FileBrowser = FileBrowserActor; +} + +void UDirectoryViewerButton::OnClick() +{ + FileBrowser->BaseForwardDirectory(TargetDirectoryName); + return; +} diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/DirectoryViewerGrid.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/DirectoryViewerGrid.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfe39c9a4aa111f3d043c22efa658b3440a69b09 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/DirectoryViewerGrid.cpp @@ -0,0 +1,72 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "DirectoryViewerGrid.h" +#include "DirectoryViewerButton.h" +#include "Blueprint/WidgetTree.h" +#include "Components/SizeBox.h" + +void UDirectoryViewerGrid::NativeConstruct() +{ + Super::NativeConstruct(); + + UE_LOG(LogTemp, Display, TEXT("It's working!")); + + NavigateBackwardButton->OnClicked.AddDynamic(this, &UDirectoryViewerGrid::OnClick); +} + +void UDirectoryViewerGrid::SetFileBrowserActor(AFileBrowser *FileBrowserActorPointer) +{ + FileBrowserActor = FileBrowserActorPointer; +} + + +void UDirectoryViewerGrid::CreateDirectoryButton(int32 index, FString FolderName) +{ + + if (!DirectoryWrapBox) + return; + + UE_LOG(LogTemp, Display, TEXT("executing CreateDirectoryButton")); + UDirectoryViewerButton *NewButton = CreateWidget<UDirectoryViewerButton>(GetWorld(), DirectoryViewerButtonClass); + if (!NewButton) + return; + + NewButton->SetDirectoryName(FolderName); + NewButton->SetFileBrowserActor(FileBrowserActor); + + USizeBox *SizeBox = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass()); + + if (!SizeBox) + return; + + SizeBox->AddChild(NewButton); + + SizeBox->SetHeightOverride(90); + SizeBox->SetWidthOverride(400); + + DirectoryWrapBox->AddChildToWrapBox(SizeBox); +} + +void UDirectoryViewerGrid::ClearDirectoryButtons() +{ + if (!DirectoryWrapBox) + return; + + DirectoryWrapBox->ClearChildren(); +} + +void UDirectoryViewerGrid::SetCurrentDirectoryText(FString DirectoryText) +{ + if (!CurrentDirectoryText) + return; + + CurrentDirectoryText->SetText(FText::FromString(DirectoryText)); +} + +void UDirectoryViewerGrid::OnClick() +{ + if (!FileBrowserActor) + return; + FileBrowserActor->BaseBackwardDirectory(); +} \ No newline at end of file diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/FileBrowser.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/FileBrowser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f988182eafa4e79d7c693df6833637465c371531 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/FileBrowser.cpp @@ -0,0 +1,256 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "FileBrowser.h" +#include "ImageViewerGrid.h" +#include "AssetViewerGrid.h" +#include "DirectoryViewerGrid.h" + +// Sets default values +AFileBrowser::AFileBrowser() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + + Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root")); + SetRootComponent(Root); + + BaseMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("BaseMesh")); + BaseMesh->SetupAttachment(Root); + + ImageDisplayMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DisplayMesh")); + ImageDisplayMesh->SetupAttachment(Root); + + AssetDisplayMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("AssetDisplayMesh")); + AssetDisplayMesh->SetupAttachment(Root); + + FileStructureDisplayMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("FileStructureDisplayMesh")); + FileStructureDisplayMesh->SetupAttachment(Root); + + ImageSpawnerMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ImageSpawnerMesh")); + ImageSpawnerMesh->SetupAttachment(Root); + + ImageViewerGrid = CreateDefaultSubobject<UWidgetComponent>(TEXT("ImageViewerGrid")); + ImageViewerGrid->SetupAttachment(Root); + + DirectoryViewerGrid = CreateDefaultSubobject<UWidgetComponent>(TEXT("DirectoryViewerGrid")); + DirectoryViewerGrid->SetupAttachment(Root); + + AssetViewerGrid = CreateDefaultSubobject<UWidgetComponent>(TEXT("AssetViewerGrid")); + AssetViewerGrid->SetupAttachment(Root); + + ImageFileExtensions = {".bmp", ".float", ".pcx", ".png", ".psd", ".tga", ".jpg", ".exr"}; + + bCanGoBackward = true; +} + +// Called when the game starts or when spawned +void AFileBrowser::BeginPlay() +{ + Super::BeginPlay(); + + CurrentDirectory = FPaths::ProjectContentDir() + TEXT("ArchData/*"); + MainDirectory = FPaths::ProjectContentDir() + TEXT("ArchData/*"); + FFileManagerGeneric FileManager; + + UDirectoryViewerGrid *DirectoryGridWidget = Cast<UDirectoryViewerGrid>(DirectoryViewerGrid->GetUserWidgetObject()); + + FileManager.FindFiles(CurrentDirectoryFolders, *CurrentDirectory, false, true); + + if (DirectoryGridWidget) + { + DirectoryGridWidget->SetFileBrowserActor(this); + + for (FString Folder : CurrentDirectoryFolders) + { + UE_LOG(LogTemp, Display, TEXT("folder %s exists"), *Folder); + } + + for (int i = 0; i < CurrentDirectoryFolders.Num(); i++) + { + DirectoryGridWidget->CreateDirectoryButton(i, CurrentDirectoryFolders[i]); + } + } +} + +void AFileBrowser::ForwardDirectory(FString FolderName) +{ + CurrentDirectory = CurrentDirectory.Replace(TEXT("*"), *FolderName) + "/*"; + FString ImageAssetDirectory = CurrentDirectory.Replace(TEXT("*"), TEXT("")); + + UDirectoryViewerGrid *DirectoryGridWidget = Cast<UDirectoryViewerGrid>(DirectoryViewerGrid->GetUserWidgetObject()); + UImageViewerGrid *ImageViewerGridWidget = Cast<UImageViewerGrid>(ImageViewerGrid->GetUserWidgetObject()); + UAssetViewerGrid *AssetViewerGridWidget = Cast<UAssetViewerGrid>(AssetViewerGrid->GetUserWidgetObject()); + + if (DirectoryGridWidget) + { + DirectoryGridWidget->SetCurrentDirectoryText(CurrentDirectory.RightChop(MainDirectory.Len() - 1)); + + DirectoryGridWidget->ClearDirectoryButtons(); + CurrentDirectoryFolders.Empty(); + + FFileManagerGeneric FileManager; + FileManager.FindFiles(CurrentDirectoryFolders, *CurrentDirectory, false, true); + + for (int i = 0; i < CurrentDirectoryFolders.Num(); i++) + { + DirectoryGridWidget->CreateDirectoryButton(i, CurrentDirectoryFolders[i]); + } + + if (AssetViewerGridWidget) + { + AssetViewerGridWidget->ClearAssetButtons(); + CurrentDirectoryAssets.Empty(); + + // FileManager.FindFiles(CurrentDirectoryAssets, *ImageAssetDirectory, TEXT("*.uasset")); + FileManager.FindFiles(CurrentDirectoryAssets, *ImageAssetDirectory, TEXT("*.mesh")); + for (FString &Asset : CurrentDirectoryAssets) + { + Asset = Asset.Replace(TEXT(".mesh"), TEXT(".uasset")); + } + + for (int i = 0; i < CurrentDirectoryAssets.Num(); i++) + { + AssetViewerGridWidget->CreateAssetButton(i, CurrentDirectoryAssets[i], ImageAssetDirectory + CurrentDirectoryAssets[i], this); + } + } + + if (ImageViewerGridWidget) + { + ImageViewerGridWidget->ClearImageButtons(); + CurrentDirectoryImages.Empty(); + + for (FString Extension : ImageFileExtensions) + { + TArray<FString> ImageTypeSpecifiedFileNames; + FileManager.FindFiles(ImageTypeSpecifiedFileNames, *ImageAssetDirectory, *Extension); + for (FString &Name : ImageTypeSpecifiedFileNames) + { + CurrentDirectoryImages.Add(Name); + } + } + + if (CurrentDirectoryAssets.Num() == 0) + { + + if (CurrentDirectoryImages.Num() > 0) + { + FTimerHandle TimerHandle; + bCanGoBackward = false; + GetWorldTimerManager().SetTimer(TimerHandle, this, &AFileBrowser::AllowBackwardDirectory, 10); + for (int i = 0; i < CurrentDirectoryImages.Num(); i++) + { + ImageViewerGridWidget->CreateImageButton(i, CurrentDirectoryImages[i], ImageAssetDirectory + CurrentDirectoryImages[i], this); + } + } + } + } + } + + return; +} + +void AFileBrowser::BackwardDirectory() +{ + if (!bCanGoBackward) + { + return; + } + UDirectoryViewerGrid *DirectoryGridWidget = Cast<UDirectoryViewerGrid>(DirectoryViewerGrid->GetUserWidgetObject()); + UImageViewerGrid *ImageViewerGridWidget = Cast<UImageViewerGrid>(ImageViewerGrid->GetUserWidgetObject()); + UAssetViewerGrid *AssetViewerGridWidget = Cast<UAssetViewerGrid>(AssetViewerGrid->GetUserWidgetObject()); + + if (DirectoryGridWidget) + { + if (CurrentDirectory == MainDirectory) + { + return; + } + int32 LastSlashIndex; + CurrentDirectory.LeftChop(2).FindLastChar('/', LastSlashIndex); + FString ParentDirectory = CurrentDirectory.LeftChop(CurrentDirectory.Len() - LastSlashIndex) + "/*"; + CurrentDirectory = ParentDirectory; + UE_LOG(LogTemp, Display, TEXT("current directory is %s"), *CurrentDirectory); + DirectoryGridWidget->SetCurrentDirectoryText(CurrentDirectory.RightChop(MainDirectory.Len() - 1)); + DirectoryGridWidget->ClearDirectoryButtons(); + CurrentDirectoryFolders.Empty(); + FFileManagerGeneric FileManager; + FileManager.FindFiles(CurrentDirectoryFolders, *CurrentDirectory, false, true); + + for (int i = 0; i < CurrentDirectoryFolders.Num(); i++) + { + DirectoryGridWidget->CreateDirectoryButton(i, CurrentDirectoryFolders[i]); + } + + if (ImageViewerGridWidget) + { + ImageViewerGridWidget->ClearImageButtons(); + CurrentDirectoryImages.Empty(); + } + + if (AssetViewerGridWidget) + { + AssetViewerGridWidget->ClearAssetButtons(); + CurrentDirectoryAssets.Empty(); + } + } + + return; +} + +// Called every frame +void AFileBrowser::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} + +void AFileBrowser::AllowBackwardDirectory() +{ + bCanGoBackward = true; +} + +void AFileBrowser::BaseForwardDirectory(FString TargetDirectoryName_) +{ + //Server/Multicast can only be called in AActor type classes, so we implement here instead of UWidget + ServerForwardDirectory(TargetDirectoryName_); +} + +bool AFileBrowser::ServerForwardDirectory_Validate(const FString& TargetDirectoryName_) +{ + return true; // This will allow the RPC to be called +} + +void AFileBrowser::ServerForwardDirectory_Implementation(const FString& TargetDirectoryName_) +{ + MulticastForwardDirectory(TargetDirectoryName_); + return; +} + +bool AFileBrowser::MulticastForwardDirectory_Validate(const FString& TargetDirectoryName_) +{ + return true; // This will allow the RPC to be called +} + +void AFileBrowser::MulticastForwardDirectory_Implementation(const FString& TargetDirectoryName_) +{ + FString result = "Result InMulti: " + TargetDirectoryName_; + ForwardDirectory(TargetDirectoryName_); + return; +} + +void AFileBrowser::BaseBackwardDirectory() +{ + ServerBackwardDirectory(); +} + +void AFileBrowser::ServerBackwardDirectory_Implementation() +{ + MulticastBackwardDirectory(); + return; +} + +void AFileBrowser::MulticastBackwardDirectory_Implementation() +{ + + BackwardDirectory(); + return; +} \ No newline at end of file diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/ImageViewerButton.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/ImageViewerButton.cpp new file mode 100644 index 0000000000000000000000000000000000000000..830bfc21894094f633e43ca9c7f177d480c3a974 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/ImageViewerButton.cpp @@ -0,0 +1,183 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "ImageViewerButton.h" +#include "UObject/UObjectGlobals.h" + +void UImageViewerButton::NativeConstruct() +{ + Super::NativeConstruct(); + + // OptionButton->OnClicked.AddDynamic(this, &UImageViewerButton::OnClick); +} + +void UImageViewerButton::NativeDestruct() +{ + Super::NativeDestruct(); + + if (ImageTexture && ImageTexture->IsValidLowLevel()) + { + ImageTexture->RemoveFromRoot(); + ImageTexture->GetPlatformData()->Mips[0].BulkData.RemoveBulkData(); + ImageTexture->ConditionalBeginDestroy(); + ImageTexture = nullptr; + } +} + +// void UImageViewerButton::OnClick() +// { +// // UE_LOG(LogTemp, Display, TEXT("Button for %s clicked"), *TargetImageName); +// if (!ImageTexture || !ImageTexture->IsValidLowLevel()) +// return; + +// } + +void UImageViewerButton::SetImageName(FString ImageName) +{ + if (!ImageNameText) + return; + + ImageNameText->SetText(FText::FromString(ImageName)); + TargetImageName = ImageName; +} + +void UImageViewerButton::SetImagePath(FString ImagePath) +{ + if (!Image) + return; + + // int32 FolderSubstringIndex = ImagePath.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd); + // int32 ArchDataFolderIndex = ImagePath.Find(TEXT("ArchData"), ESearchCase::IgnoreCase, ESearchDir::FromStart); + // ImagePath = ImagePath.LeftChop(ImagePath.Len() - (FolderSubstringIndex + 1)); + // ImagePath = ImagePath.RightChop(ArchDataFolderIndex); + // ImagePath = "/Game/" + ImagePath; + + TargetImagePath = ImagePath; + + RunLoadingImageFromDiskTask(TargetImagePath, Image, this); + + if (!ImageTexture) + return; + + Image->SetBrushFromTexture(ImageTexture, false); +} + +void UImageViewerButton::SetSpawner(AFileBrowser *FileBrowser) +{ + Spawner = FileBrowser; +} + +void UImageViewerButton::RunLoadingImageFromDiskTask(FString TaskImagePath, UImage *TaskImage, UImageViewerButton *TaskImageViewerButton) +{ + (new FAutoDeleteAsyncTask<LoadingImageFromDiskTask>(TaskImagePath, TaskImage, TaskImageViewerButton))->StartBackgroundTask(); +} + +// ================================================================================================= + +LoadingImageFromDiskTask::LoadingImageFromDiskTask(FString ImagePath, UImage *Image, UImageViewerButton *ImageViewerButton) +{ + TargetImagePath = ImagePath; + TargetImage = Image; + TargetImageViewerButton = ImageViewerButton; +} + +LoadingImageFromDiskTask::~LoadingImageFromDiskTask() +{ + UE_LOG(LogTemp, Warning, TEXT("LoadingImageFromDiskTask Completed")); +} + +void LoadingImageFromDiskTask::DoWork() +{ + if (TargetImagePath.IsEmpty() || !TargetImage->IsValidLowLevel() || !TargetImageViewerButton->IsValidLowLevel()) + return; + + // Check if the file exists first + if (!FPaths::FileExists(TargetImagePath)) + { + UE_LOG(LogTemp, Error, TEXT("File not found: %s"), *TargetImagePath); + } + + // Load the compressed byte data from the file + TArray<uint8> FileData; + if (!FFileHelper::LoadFileToArray(FileData, *TargetImagePath)) + { + UE_LOG(LogTemp, Error, TEXT("Failed to load file: %s"), *TargetImagePath); + } + + // Detect the image type using the ImageWrapper module + IImageWrapperModule &ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper")); + + EImageFormat ImageFormat = ImageWrapperModule.DetectImageFormat(FileData.GetData(), FileData.Num()); + if (ImageFormat == EImageFormat::Invalid) + { + UE_LOG(LogTemp, Error, TEXT("Unrecognized image file format: %s"), *TargetImagePath); + } + + // Create an image wrapper for the detected image format + + TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(ImageFormat); + if (!ImageWrapper.IsValid()) + { + UE_LOG(LogTemp, Error, TEXT("Failed to create image wrapper for file: %s"), *TargetImagePath); + } + + TArray<uint8> RawImageData; + + ImageWrapper->SetCompressed(FileData.GetData(), FileData.Num()); + ImageWrapper->GetRaw(ERGBFormat::BGRA, 8, RawImageData); + + if (RawImageData.Num() == 0) + { + UE_LOG(LogTemp, Error, TEXT("Failed to decompress image file: %s"), *TargetImagePath); + } + + // Create the texture and upload the uncompressed image data + FString TextureBaseName = TEXT("Texture_") + FPaths::GetBaseFilename(TargetImagePath); + + int32 InSizeX = ImageWrapper->GetWidth(); + int32 InSizeY = ImageWrapper->GetHeight(); + EPixelFormat InFormat = EPixelFormat::PF_B8G8R8A8; + FName BaseName = FName(*TextureBaseName); + // Shamelessly copied from UTexture2D::CreateTransient with a few modifications + if (InSizeX <= 0 || InSizeY <= 0 || + (InSizeX % GPixelFormats[InFormat].BlockSizeX) != 0 || + (InSizeY % GPixelFormats[InFormat].BlockSizeY) != 0) + { + UE_LOG(LogTemp, Warning, TEXT("Invalid parameters specified for UFrameGrabber::CreateTexture()")); + } + + UE_LOG(LogTemp, Display, TEXT("executing LoadImageAsTextureFromDisk")); + + UTexture2D *LoadedT2D = UTexture2D::CreateTransient(InSizeX, InSizeY, InFormat); + + // Most important difference with UTexture2D::CreateTransient: we provide the new texture with a name and an owner + FName TextureName = MakeUniqueObjectName(LoadedT2D, UTexture2D::StaticClass(), BaseName); + UTexture2D *NewTexture = NewObject<UTexture2D>(LoadedT2D, TextureName, RF_Transient); + + NewTexture->SetPlatformData(new FTexturePlatformData()); + NewTexture->GetPlatformData()->SizeX = InSizeX; + NewTexture->GetPlatformData()->SizeY = InSizeY; + NewTexture->GetPlatformData()->PixelFormat = InFormat; + + // Allocate first mipmap and upload the pixel data + int32 NumBlocksX = InSizeX / GPixelFormats[InFormat].BlockSizeX; + int32 NumBlocksY = InSizeY / GPixelFormats[InFormat].BlockSizeY; + + //Deprecated not sure new method if needed + //FTexture2DMipMap* Mip = new (NewTexture->GetPlatformData()->Mips) FTexture2DMipMap(); + FTexture2DMipMap* Mip = new FTexture2DMipMap(); + + + + Mip->SizeX = InSizeX; + Mip->SizeY = InSizeY; + Mip->BulkData.Lock(LOCK_READ_WRITE); + void *TextureData = Mip->BulkData.Realloc(NumBlocksX * NumBlocksY * GPixelFormats[InFormat].BlockBytes); + FMemory::Memcpy(TextureData, RawImageData.GetData(), RawImageData.Num()); + Mip->BulkData.Unlock(); + + NewTexture->UpdateResource(); + // return NewTexture; + + TargetImage->SetBrushFromTexture(NewTexture); + TargetImageViewerButton->ImageTexture = NewTexture; +} diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/ImageViewerGrid.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/ImageViewerGrid.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0988ed1c05339cfed5b1702d618f40f151d17db7 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/ImageViewerGrid.cpp @@ -0,0 +1,71 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "ImageViewerGrid.h" +#include "Components/SizeBox.h" +#include "Blueprint/WidgetTree.h" +#include "Misc/IQueuedWork.h" + +void UImageViewerGrid::NativeConstruct() +{ + Super::NativeConstruct(); +} + +void UImageViewerGrid::CreateImageButton(int32 index, FString ImageName, FString ImagePath, AFileBrowser *FileBrowser) +{ + if (!ImageWrapBox) + return; + + UImageViewerButton *NewButton = CreateWidget<UImageViewerButton>(GetWorld(), ImageViewerButtonClass); + if (!NewButton) + return; + + NewButton->SetImageName(ImageName); + NewButton->SetImagePath(ImagePath); + NewButton->SetSpawner(FileBrowser); + + USizeBox *SizeBox = WidgetTree->ConstructWidget<USizeBox>(USizeBox::StaticClass()); + + if (!SizeBox) + return; + + SizeBox->AddChild(NewButton); + + SizeBox->SetHeightOverride(1000); + SizeBox->SetWidthOverride(520); + + ImageWrapBox->AddChildToWrapBox(SizeBox); +} + +void UImageViewerGrid::ClearImageButtons() +{ + if (!ImageWrapBox) + return; + + for (int32 i = 0; i < ImageWrapBox->GetChildrenCount(); i++) + { + USizeBox *SizeBox = Cast<USizeBox>(ImageWrapBox->GetChildAt(i)); + if (!SizeBox) + continue; + + UImageViewerButton *Button = Cast<UImageViewerButton>(SizeBox->GetChildAt(0)); + if (!Button) + continue; + + Button->Image->RemoveFromRoot(); + Button->Image->ConditionalBeginDestroy(); + + UTexture2D *Texture = Button->ImageTexture; + + Button->ImageTexture->RemoveFromRoot(); + Texture->GetPlatformData()->Mips[0].BulkData.RemoveBulkData(); + Button->ImageTexture->ConditionalBeginDestroy(); + Button->ImageTexture = nullptr; + + Button->RemoveFromRoot(); + Button->ConditionalBeginDestroy(); + } + + ImageWrapBox->ClearChildren(); + GEngine->ForceGarbageCollection(true); + return; +} diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/LevelList.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/LevelList.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f4d9157b39116a1d42b93cbb189b538abfa76b95 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/LevelList.cpp @@ -0,0 +1,34 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "LevelList.h" +#include "Components/SizeBox.h" + +void ULevelList::NativeConstruct() { + Super::NativeConstruct(); +} + + +void ULevelList:: CreateLevelButton(int32 index, FString MapName) { + if (!LevelList) return; + + ULevelListOption *NewWidget = CreateWidget<ULevelListOption>(GetWorld(), LevelListOptionClass); + if (!NewWidget) return; + + NewWidget->SetLevelName(MapName); + + USizeBox *SizeBox = Cast<USizeBox> (LevelList->GetChildAt(index)); + if (!SizeBox) return; + + SizeBox->AddChild(NewWidget); +} + +void ULevelList::ClearLevelButtons() { + if (!LevelList) return; + + for (int32 i = 0; i < LevelList->GetChildrenCount(); i++) { + USizeBox *SizeBox = Cast<USizeBox> (LevelList->GetChildAt(i)); + if (!SizeBox) return; + SizeBox->ClearChildren(); + } +} diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/LevelListOption.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/LevelListOption.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3d9d05f89ca8631c8742466e5efb83c6f144e0c5 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/LevelListOption.cpp @@ -0,0 +1,29 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "LevelListOption.h" +#include "Kismet/GameplayStatics.h" +#include "Engine/World.h" + +void ULevelListOption::NativeConstruct() +{ + Super::NativeConstruct(); + + OptionButton->OnClicked.AddDynamic(this, &ULevelListOption::OnOptionButtonClicked); +} + +void ULevelListOption::SetLevelName(FString LevelName) +{ + if (!MapNameText) + return; + + MapNameText->SetText(FText::FromString(LevelName)); + TargetLevelName = LevelName; +} + +void ULevelListOption::OnOptionButtonClicked() +{ + UE_LOG(LogTemp, Display, TEXT("executed with name %s"), *TargetLevelName); + FString MapPath = FPaths::ProjectContentDir() + "Rooms/"; + + UGameplayStatics::OpenLevel(GetWorld(), FName(*(MapPath + TargetLevelName))); +} \ No newline at end of file diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/PickUpArtifact.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/PickUpArtifact.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2734538617a52d053669d80d423532d4f327183d --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/PickUpArtifact.cpp @@ -0,0 +1,45 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "PickUpArtifact.h" +#include "WarehouseFunctionLibrary.h" + +// Sets default values +APickUpArtifact::APickUpArtifact() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + + Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root")); + SetRootComponent(Root); + + ArtifactMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ArtifactMesh")); + ArtifactMeshComponent->SetupAttachment(Root); +} + +void APickUpArtifact::SetArtifactComponentMesh() +{ + int32 FolderSubstringIndex = TargetArtifactName.Find(TEXT("/"), ESearchCase::IgnoreCase, ESearchDir::FromEnd); + int32 ArchDataFolderIndex = TargetArtifactName.Find(TEXT("ArchData"), ESearchCase::IgnoreCase, ESearchDir::FromStart); + TargetArtifactName = TargetArtifactName.LeftChop(TargetArtifactName.Len() - (FolderSubstringIndex + 1)); + TargetArtifactName = TargetArtifactName.RightChop(ArchDataFolderIndex); + TargetArtifactName = "/Game/" + TargetArtifactName; + // TargetArtifactName = "/Game/ArchData/Israel/Site/Tel_Dor/2017/3D_Models/Object/Mesh/NMM17_Anchor_A/"; + UE_LOG(LogTemp, Display, TEXT("SetArtifactComponentMesh called on %s"), *TargetArtifactName); + TargetArtifactMesh = UWarehouseFunctionLibrary::LoadAssets(TargetArtifactName); + ArtifactMeshComponent->SetStaticMesh(TargetArtifactMesh); +} + +// Called when the game starts or when spawned +void APickUpArtifact::BeginPlay() +{ + Super::BeginPlay(); + +} + +// Called every frame +void APickUpArtifact::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} + diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/PickUpImage.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/PickUpImage.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bcd72e6f284a22e2aeecdbf4492c09b9fe038603 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/PickUpImage.cpp @@ -0,0 +1,33 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "PickUpImage.h" + +// Sets default values +APickUpImage::APickUpImage() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + + Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root")); + SetRootComponent(Root); + + ImageMeshComponent = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ImageMeshComponent")); + ImageMeshComponent->SetupAttachment(Root); +} + +UTexture2D *APickUpImage::GetTexture() +{ + return Texture; +} + +// Called when the game starts or when spawned +void APickUpImage::BeginPlay() +{ + Super::BeginPlay(); +} + +// Called every frame +void APickUpImage::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/UITable.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/UITable.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e0cc3c25d065900df45a114d73cde6ad13b3c1af --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/UITable.cpp @@ -0,0 +1,264 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "UITable.h" +#include "LevelList.h" +#include "Kismet/GameplayStatics.h" +#include "Engine/World.h" +#include "Components/Button.h" +#include "TimerManager.h" +#include "Misc/Paths.h" + +// Sets default values +AUITable::AUITable() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + + Root = CreateDefaultSubobject<USceneComponent>(TEXT("Root")); + SetRootComponent(Root); + + TableMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("TableMesh")); + TableMesh->SetupAttachment(Root); + + DisplayMesh = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("DisplayMesh")); + DisplayMesh->SetupAttachment(Root); + + LevelList = CreateDefaultSubobject<UWidgetComponent>(TEXT("LevelList")); + LevelList->SetupAttachment(Root); + + // Pagination + PreviousPageButton = CreateDefaultSubobject<USphereComponent>(TEXT("PreviousPageButton")); + PreviousPageButton->SetupAttachment(Root); + NextPageButton = CreateDefaultSubobject<USphereComponent>(TEXT("NextPageButton")); + NextPageButton->SetupAttachment(Root); + + // Buttons + Sphere1 = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere1")); + Sphere2 = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere2")); + Sphere3 = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere3")); + Sphere4 = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere4")); + Sphere5 = CreateDefaultSubobject<USphereComponent>(TEXT("Sphere5")); + + Sphere1->SetupAttachment(Root); + Sphere2->SetupAttachment(Root); + Sphere3->SetupAttachment(Root); + Sphere4->SetupAttachment(Root); + Sphere5->SetupAttachment(Root); +} + +void AUITable::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} + +// Called when the game starts or when spawned +void AUITable::BeginPlay() +{ + Super::BeginPlay(); + + Sphere1->OnComponentBeginOverlap.AddDynamic(this, &AUITable::OnOptionsOverlapBegin); + Sphere2->OnComponentBeginOverlap.AddDynamic(this, &AUITable::OnOptionsOverlapBegin); + Sphere3->OnComponentBeginOverlap.AddDynamic(this, &AUITable::OnOptionsOverlapBegin); + Sphere4->OnComponentBeginOverlap.AddDynamic(this, &AUITable::OnOptionsOverlapBegin); + Sphere5->OnComponentBeginOverlap.AddDynamic(this, &AUITable::OnOptionsOverlapBegin); + + PreviousPageButton->OnComponentBeginOverlap.AddDynamic(this, &AUITable::OnPageButtonsOverlapBegin); + NextPageButton->OnComponentBeginOverlap.AddDynamic(this, &AUITable::OnPageButtonsOverlapBegin); + + FFileManagerGeneric FileManager; + MapPath = FPaths::ProjectContentDir() + "Rooms/"; + FileManager.FindFiles(MapNames, *MapPath, TEXT(".umap")); + MapNames.Remove("MainMenu2D.umap"); + UE_LOG(LogTemp, Display, TEXT("Length of MapNames: %d"), MapNames.Num()); + + for (FString MapName : MapNames) + { + UE_LOG(LogTemp, Display, TEXT("Map %s exists"), *MapName); + } + + for (int i = StartIndex; i <= EndIndex; i++) + { + CurrentPageLevels.Add(MapNames[i]); + } + + ULevelList *LevelListWidget = Cast<ULevelList>(LevelList->GetUserWidgetObject()); + if (LevelListWidget) + { + for (int i = 0; i <= EndIndex; i++) + { + LevelListWidget->CreateLevelButton(i, CurrentPageLevels[i]); + } + } + + UButton *LevelNextPage = Cast<UButton>(LevelListWidget->GetWidgetFromName(TEXT("LevelNextPage"))); + UButton *LevelPreviousPage = Cast<UButton>(LevelListWidget->GetWidgetFromName(TEXT("LevelPreviousPage"))); + + if (LevelNextPage && LevelPreviousPage) + { + LevelNextPage->OnClicked.AddDynamic(this, &AUITable::OnNextPageClicked); + LevelPreviousPage->OnClicked.AddDynamic(this, &AUITable::OnPreviousPageClicked); + } +} + +void AUITable::GoToNextPage() +{ + ULevelList *LevelListWidget = Cast<ULevelList>(LevelList->GetUserWidgetObject()); + if (LevelListWidget) + { + if (StartIndex + 5 < MapNames.Num()) + { + StartIndex += 5; + } + if (EndIndex + 4 < MapNames.Num()) + { + EndIndex += 4; + } + else + { + EndIndex = MapNames.Num() - 1; + } + UE_LOG(LogTemp, Display, TEXT("StartIndex: %d"), StartIndex); + UE_LOG(LogTemp, Display, TEXT("EndIndex: %d"), EndIndex); + + CurrentPageLevels.Empty(); + for (int i = StartIndex; i <= EndIndex; i++) + { + CurrentPageLevels.Add(MapNames[i]); + } + + LevelListWidget->ClearLevelButtons(); + + for (int i = 0; i < (EndIndex - StartIndex + 1); i++) + { + LevelListWidget->CreateLevelButton(i, CurrentPageLevels[i]); + } + } +} + +void AUITable::GoToPreviousPage() +{ + ULevelList *LevelListWidget = Cast<ULevelList>(LevelList->GetUserWidgetObject()); + if (LevelListWidget) + { + if (StartIndex - 5 >= 0) + { + StartIndex -= 5; + EndIndex = StartIndex + 4; + UE_LOG(LogTemp, Display, TEXT("StartIndex: %d"), StartIndex); + UE_LOG(LogTemp, Display, TEXT("EndIndex: %d"), EndIndex); + } + + CurrentPageLevels.Empty(); + for (int i = StartIndex; i <= EndIndex; i++) + { + CurrentPageLevels.Add(MapNames[i]); + } + + LevelListWidget->ClearLevelButtons(); + + for (int i = 0; i <= (EndIndex - StartIndex); i++) + { + LevelListWidget->CreateLevelButton(i, CurrentPageLevels[i]); + } + } +} + +void AUITable::OnOptionsOverlapBegin(class UPrimitiveComponent *OverlappedComp, class AActor *OtherActor, + class UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult &SweepResult) +{ + if (IsOptionOverlapping == false) + { + AUserMotionController *MotionController = Cast<AUserMotionController>(OtherActor); + if (MotionController && OtherComp->GetName() == "PointerSphere") + { + FString OverlappedButtonName = OverlappedComp->GetName(); + if (OverlappedButtonName == "Sphere1") + { + if (CurrentPageLevels.Num() < 1) + return; + ChangeLevel(MapPath + CurrentPageLevels[0]); + } + else if (OverlappedButtonName == "Sphere2") + { + if (CurrentPageLevels.Num() < 2) + return; + ChangeLevel(MapPath + CurrentPageLevels[1]); + } + else if (OverlappedButtonName == "Sphere3") + { + ChangeLevel(MapPath + CurrentPageLevels[2]); + } + else if (OverlappedButtonName == "Sphere4") + { + if (CurrentPageLevels.Num() < 4) + return; + ChangeLevel(MapPath + CurrentPageLevels[3]); + } + else if (OverlappedButtonName == "Sphere5") + { + if (CurrentPageLevels.Num() < 5) + return; + ChangeLevel(MapPath + CurrentPageLevels[4]); + } + + FTimerHandle TimerHandle; + IsOptionOverlapping = true; + GetWorldTimerManager().SetTimer(TimerHandle, this, &AUITable::AllowOptionsOverlap, 2); + } + } +} + +void AUITable::OnPageButtonsOverlapBegin(class UPrimitiveComponent *OverlappedComp, class AActor *OtherActor, + class UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult &SweepResult) +{ + if (IsPageButtonOverlapping == false) + { + AUserMotionController *MotionController = Cast<AUserMotionController>(OtherActor); + if (MotionController && OtherComp->GetName() == "PointerSphere") + { + FString OverlappedButtonName = OverlappedComp->GetName(); + if (OverlappedButtonName == "PreviousPageButton") + { + UE_LOG(LogTemp, Display, TEXT("overlapping previous page button")); + GoToPreviousPage(); + } + else if (OverlappedButtonName == "NextPageButton") + { + UE_LOG(LogTemp, Display, TEXT("overlapping next page button")); + GoToNextPage(); + } + FTimerHandle TimerHandle; + IsPageButtonOverlapping = true; + GetWorldTimerManager().SetTimer(TimerHandle, this, &AUITable::AllowPageButtonsOverlap, 2); + } + } +} + +void AUITable::ChangeLevel(FString LevelName) +{ + UGameplayStatics::OpenLevel(GetWorld(), FName(*LevelName)); +} + +void AUITable::AllowOptionsOverlap() +{ + IsOptionOverlapping = false; +} + +void AUITable::AllowPageButtonsOverlap() +{ + IsPageButtonOverlapping = false; +} + +void AUITable::OnNextPageClicked() +{ + UE_LOG(LogTemp, Display, TEXT("Hello from OnNextPageClicked")); + GoToNextPage(); +} + +void AUITable::OnPreviousPageClicked() +{ + UE_LOG(LogTemp, Display, TEXT("Hello from OnPreviousPageClicked")); + GoToPreviousPage(); +} diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/UserMotionController.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/UserMotionController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..683203c0d4769af0d3924cb48ff070cd3fd4768f --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/UserMotionController.cpp @@ -0,0 +1,27 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "UserMotionController.h" + +// Sets default values +AUserMotionController::AUserMotionController() +{ + // Set this actor to call Tick() every frame. You can turn this off to improve performance if you don't need it. + PrimaryActorTick.bCanEverTick = true; + +} + +// Called when the game starts or when spawned +void AUserMotionController::BeginPlay() +{ + Super::BeginPlay(); + +} + +// Called every frame +void AUserMotionController::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + +} + diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/WarehouseFunctionLibrary.cpp b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/WarehouseFunctionLibrary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b8d7c44694a5a8af02327dc0e25b9d7ec5294645 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Private/WarehouseFunctionLibrary.cpp @@ -0,0 +1,85 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "WarehouseFunctionLibrary.h" +#include "EngineUtils.h" + +UWarehouseFunctionLibrary::UWarehouseFunctionLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} +TArray<FTrackedData> UWarehouseFunctionLibrary::LoadTrackedData(FString FilePath) +{ + //TArray<FVector> data; + TArray<FTrackedData> data; + FString result; + FString finalPath = FilePath; + if (FFileHelper::LoadFileToString(result, *finalPath)) + { + + TArray<FString> stringData; + FString comma = "\n"; + int32 num = 0; + num = result.ParseIntoArray(stringData, *comma); + + for (int i = 1; i < stringData.Num(); i++) + { + //GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, stringData[i]); + comma = ","; + TArray<FString> lineData; + + num = stringData[i].ParseIntoArray(lineData, *comma); + FTrackedData item; + //GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, lineData[2]); + item.timestamp = lineData[0]; + item.xG = (FCString::Atod(*lineData[1])); + item.yG = (FCString::Atod(*lineData[2])); + item.xP = (FCString::Atod(*lineData[3])); + item.yP = (FCString::Atod(*lineData[4])); + + data.Add(item); + } + } + return data; +} +UStaticMesh* UWarehouseFunctionLibrary::LoadAssets(FString filename) +{ + FString debugText = "Hello World"; + UStaticMesh* loadedMesh = nullptr; + + if (true) + { + TArray<UObject*> MeshAssets; + EngineUtils::FindOrLoadAssetsByPath(filename, MeshAssets, EngineUtils::ATL_Regular); + + debugText = "Loaded Assets:" + FString::FromInt(MeshAssets.Num()); + GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, debugText); + for (UObject* Asset : MeshAssets) + { + if (Asset->IsA(UStaticMesh::StaticClass())) + { + loadedMesh = Cast<UStaticMesh>(Asset); + debugText = "Loaded Mesh:" + loadedMesh->GetName(); + GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, debugText); + break; + } + } + + // if (MeshAssets.Num() > 0) + // { + // loadedMesh = Cast<UStaticMesh>(MeshAssets[0]); + // if (loadedMesh != nullptr) + // { + // debugText = "Cast to Mesh"; + // GEngine->AddOnScreenDebugMessage(-1, 10.0f, FColor::Green, debugText); + // } + // } + } + else + { + loadedMesh = (UStaticMesh*)StaticLoadObject(UStaticMesh::StaticClass(), NULL, *filename, NULL, LOAD_None, NULL); + } + + return loadedMesh; +} + diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/AssetViewerButton.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/AssetViewerButton.h new file mode 100644 index 0000000000000000000000000000000000000000..0df2f153c9d95e84a0bfc43492e1bd6187780c96 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/AssetViewerButton.h @@ -0,0 +1,54 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/TextBlock.h" +#include "Components/Button.h" +#include "PickUpArtifact.h" +#include "FileBrowser.h" +#include "Components/StaticMeshComponent.h" +#include "AssetViewerButton.generated.h" + +/** + * + */ +UCLASS() +class UAssetViewerButton : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + + UFUNCTION() + void SetAssetName(FString AssetName); + + UFUNCTION() + void SetAssetPath(FString AssetPath); + + UFUNCTION() + void SetSpawner(AFileBrowser *FileBrowser); + + UFUNCTION(BlueprintCallable) + void OnClick(); + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UTextBlock *AssetNameText; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UButton *AssetButton; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere) + AFileBrowser *Spawner; + + UPROPERTY(BlueprintReadOnly) + FString TargetAssetName; + + UPROPERTY(BlueprintReadOnly) + FString TargetAssetPath; + + UPROPERTY(EditDefaultsOnly) + TSubclassOf<APickUpArtifact> PickUpArtifactClass; +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/AssetViewerGrid.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/AssetViewerGrid.h new file mode 100644 index 0000000000000000000000000000000000000000..20f8b8cc4f464b54b1ddf18ecf07dca29f50ebb4 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/AssetViewerGrid.h @@ -0,0 +1,38 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/WrapBox.h" +#include "Components/Button.h" +#include "AssetViewerButton.h" +#include "FileBrowser.h" +#include "AssetViewerGrid.generated.h" + +/** + * + */ +UCLASS() +class UAssetViewerGrid : public UUserWidget +{ + GENERATED_BODY() + +public: + + virtual void NativeConstruct() override; + + UFUNCTION(BlueprintCallable) + void CreateAssetButton(int32 index, FString AssetName, FString AssetPath, AFileBrowser *FileBrowser); + + UFUNCTION(BlueprintCallable) + void ClearAssetButtons(); + +protected: + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UWrapBox *AssetWrapBox; + +private: + UPROPERTY(EditDefaultsOnly, Category = "ImageViewerGrid") + TSubclassOf<UAssetViewerButton> AssetViewerButtonClass; +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/CyberArchCharacter.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/CyberArchCharacter.h new file mode 100644 index 0000000000000000000000000000000000000000..1f94565e21c4d97e973f718343128beb9a8b095d --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/CyberArchCharacter.h @@ -0,0 +1,36 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Character.h" +#include "Misc/OutputDeviceNull.h" +#include "CyberArchCharacter.generated.h" + + +UCLASS() +class ACyberArchCharacter : public ACharacter +{ + GENERATED_BODY() + +public: + // Sets default values for this character's properties + ACyberArchCharacter(); + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + +public: + // Called every frame + virtual void Tick(float DeltaTime) override; + + // Called to bind functionality to input + virtual void SetupPlayerInputComponent(class UInputComponent* PlayerInputComponent) override; + +private: + UFUNCTION() + void ChangeAvatar(int32 AvatarIndex); + + DECLARE_DELEGATE_OneParam(FInputSwitchAvatarDelegate, const int32); +}; \ No newline at end of file diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/DirectoryViewerButton.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/DirectoryViewerButton.h new file mode 100644 index 0000000000000000000000000000000000000000..9f59ecc3c48f2b5f6eef1a3759e8e85ad18a150f --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/DirectoryViewerButton.h @@ -0,0 +1,50 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/TextBlock.h" +#include "Components/Button.h" +#include "Misc/OutputDeviceNull.h" +#include "FileBrowser.h" +#include "DirectoryViewerButton.generated.h" + +/** + * + */ +UCLASS() +class UDirectoryViewerButton : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + + UFUNCTION() + void SetDirectoryName(FString DirectoryName); + + UFUNCTION() + void SetFileBrowserActor(AFileBrowser *FileBrowserActor); + + UFUNCTION() + void OnClick(); + +protected: + + + + + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UTextBlock *FolderNameText; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UButton *DirectoryButton; + + UPROPERTY(EditAnywhere) + FString TargetDirectoryName; + + UPROPERTY(EditAnywhere) + AFileBrowser *FileBrowser; +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/DirectoryViewerGrid.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/DirectoryViewerGrid.h new file mode 100644 index 0000000000000000000000000000000000000000..7cbf71a6dcfd2597dcbe74f25de3277924ce6287 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/DirectoryViewerGrid.h @@ -0,0 +1,55 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/UniformGridPanel.h" +#include "Components/WrapBox.h" +#include "Components/Button.h" +#include "DirectoryViewerButton.h" +#include "FileBrowser.h" +#include "DirectoryViewerGrid.generated.h" + +/** + * + */ +UCLASS() +class UDirectoryViewerGrid : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + + UFUNCTION(BlueprintCallable) + void CreateDirectoryButton(int32 index, FString FolderName); + + UFUNCTION(BlueprintCallable) + void ClearDirectoryButtons(); + + UFUNCTION(BlueprintCallable) + void SetFileBrowserActor(AFileBrowser *FileBrowserActor); + + UFUNCTION(BlueprintCallable) + void SetCurrentDirectoryText(FString DirectoryText); + + UFUNCTION(BlueprintCallable) + void OnClick(); + +protected: + UPROPERTY() + AFileBrowser *FileBrowserActor; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UButton *NavigateBackwardButton; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UWrapBox *DirectoryWrapBox; + + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, meta = (BindWidget)) + UTextBlock *CurrentDirectoryText; + + UPROPERTY(EditDefaultsOnly) + TSubclassOf<UDirectoryViewerButton> DirectoryViewerButtonClass; +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/FileBrowser.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/FileBrowser.h new file mode 100644 index 0000000000000000000000000000000000000000..14638f312ce4bfb7fb7d782503999b6c218a6fdc --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/FileBrowser.h @@ -0,0 +1,107 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "TimerManager.h" +#include "Components/StaticMeshComponent.h" +#include "Components/WidgetComponent.h" +#include "HAL/FileManagerGeneric.h" +#include "Components/SphereComponent.h" +#include "Engine/Texture2D.h" +#include "FileBrowser.generated.h" + +UCLASS() +class AFileBrowser : public AActor +{ + GENERATED_BODY() + +public: + AFileBrowser(); + +protected: + virtual void BeginPlay() override; + +public: + virtual void Tick(float DeltaTime) override; + + UPROPERTY(VisibleAnywhere) + USceneComponent *Root; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + UStaticMeshComponent *BaseMesh; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + UStaticMeshComponent *ImageDisplayMesh; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + UStaticMeshComponent *AssetDisplayMesh; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + UStaticMeshComponent *FileStructureDisplayMesh; + + UPROPERTY(VisibleAnywhere, BluePrintReadWrite) + UStaticMeshComponent *ImageSpawnerMesh; + + UPROPERTY(VisibleAnywhere, BluePrintReadWrite) + UWidgetComponent *ImageViewerGrid; + + UPROPERTY(VisibleAnywhere, BluePrintReadWrite) + UWidgetComponent *DirectoryViewerGrid; + + UPROPERTY(VisibleAnywhere, BluePrintReadWrite) + UWidgetComponent *AssetViewerGrid; + + UPROPERTY(EditAnywhere) + FString CurrentDirectory; + + UPROPERTY(EditAnywhere) + FString PreviousDirectory; + + UPROPERTY(EditAnywhere) + FString MainDirectory; + + UFUNCTION() + void ForwardDirectory(FString FolderName); + + UFUNCTION() + void AllowBackwardDirectory(); + + UFUNCTION() + void BackwardDirectory(); + + bool bCanGoBackward; + + UFUNCTION() + void BaseForwardDirectory(FString TargetDirectoryName_); + + UFUNCTION(Reliable, Server, WithValidation) + void ServerForwardDirectory(const FString& TargetDirectoryName_); + + UFUNCTION(Reliable, NetMulticast, WithValidation) + void MulticastForwardDirectory(const FString& TargetDirectoryName_); + + UFUNCTION() + void BaseBackwardDirectory(); + + UFUNCTION(Reliable, Server) + void ServerBackwardDirectory(); + + UFUNCTION(Reliable, NetMulticast) + void MulticastBackwardDirectory(); + +private: + UPROPERTY() + TArray<FString> ImageFileExtensions; + + UPROPERTY(VisibleAnywhere) + TArray<FString> CurrentDirectoryFolders; + + UPROPERTY(VisibleAnywhere) + TArray<FString> CurrentDirectoryImages; + + UPROPERTY(VisibleAnywhere) + TArray<FString> CurrentDirectoryAssets; + +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ICyberArchWarehouse.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ICyberArchWarehouse.h new file mode 100644 index 0000000000000000000000000000000000000000..7ccc6a18bc5e689979582dc9dd5a43269ac01b2a --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ICyberArchWarehouse.h @@ -0,0 +1,38 @@ +// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "Modules/ModuleManager.h" + + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class ICyberArchWarehouse : 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 ICyberArchWarehouse& Get() + { + return FModuleManager::LoadModuleChecked< ICyberArchWarehouse >( "CyberArchWarehouse" ); + } + + /** + * 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( "CyberArchWarehouse" ); + } +}; + diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ImageViewerButton.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ImageViewerButton.h new file mode 100644 index 0000000000000000000000000000000000000000..36ba07f08feb12e851cca44404b8667303adfcb8 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ImageViewerButton.h @@ -0,0 +1,96 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/TextBlock.h" +#include "Components/Button.h" +#include "Components/Image.h" +#include "Engine/Texture2D.h" +#include "IImageWrapper.h" +#include "IImageWrapperModule.h" +#include "RenderUtils.h" +#include "PixelFormat.h" +#include "Kismet/KismetRenderingLibrary.h" +#include "Misc/FileHelper.h" +#include "UObject/Object.h" +#include "Textures/TextureAtlas.h" +#include "Async/AsyncWork.h" +#include "Math/Vector.h" +#include "FileBrowser.h" +#include "PickUpImage.h" +#include "ImageViewerButton.generated.h" + + +class LoadingImageFromDiskTask : public FNonAbandonableTask +{ +public: + LoadingImageFromDiskTask(FString ImagePath, UImage *Image, UImageViewerButton *ImageViewerButton); + + ~LoadingImageFromDiskTask(); + + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(LoadingImageFromDiskTask, STATGROUP_ThreadPoolAsyncTasks); + } + + FString TargetImagePath; + UImage *TargetImage; + UImageViewerButton *TargetImageViewerButton; + + void DoWork(); +}; + +/** + * + */ +UCLASS() +class UImageViewerButton : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + + virtual void NativeDestruct() override; + + UFUNCTION() + void SetImageName(FString ImageName); + + UFUNCTION() + void SetImagePath(FString ImagePath); + + UFUNCTION() + void SetSpawner(AFileBrowser *Spawner); + + UFUNCTION(BlueprintCallable) + void RunLoadingImageFromDiskTask(FString TaskImagePath, UImage *TaskImage, UImageViewerButton *TaskImageViewerButton); + + // UFUNCTION(BlueprintCallable) + // void OnClick(); + + UPROPERTY(VisibleAnywhere, BluePrintReadOnly) + AFileBrowser *Spawner; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + UTexture2D *ImageTexture; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UTextBlock *ImageNameText; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UButton *OptionButton; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, meta = (BindWidget)) + UImage *Image; + + UPROPERTY(BlueprintReadOnly) + FString TargetImageName; + + UPROPERTY(BlueprintReadOnly) + FString TargetImagePath; + + UPROPERTY(EditDefaultsOnly) + TSubclassOf<APickUpImage> PickUpImageClass; +}; \ No newline at end of file diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ImageViewerGrid.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ImageViewerGrid.h new file mode 100644 index 0000000000000000000000000000000000000000..8ca1d6c1749e5236dd9e72d127447b43454c9c80 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/ImageViewerGrid.h @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/UniformGridPanel.h" +#include "Components/WrapBox.h" +#include "Components/Button.h" +#include "ImageViewerButton.h" +#include "FileBrowser.h" +#include "ImageViewerGrid.generated.h" + +/** + * + */ +UCLASS() +class UImageViewerGrid : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + + UFUNCTION(BlueprintCallable) + void CreateImageButton(int32 index, FString ImageName, FString ImagePath, AFileBrowser *FileBrowser); + + UFUNCTION(BlueprintCallable) + void ClearImageButtons(); + +protected: + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "ImageViewerGrid", meta = (BindWidget)) + UWrapBox *ImageWrapBox; + +private: + UPROPERTY(EditDefaultsOnly, Category = "ImageViewerGrid") + TSubclassOf<UImageViewerButton> ImageViewerButtonClass; + + +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/LevelList.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/LevelList.h new file mode 100644 index 0000000000000000000000000000000000000000..a7218f3e6a4d124de82fd2927f2d9a34c74d04bb --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/LevelList.h @@ -0,0 +1,42 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/UniformGridPanel.h" +#include "Components/Button.h" +#include "LevelListOption.h" +#include "LevelList.generated.h" + +/** + * + */ +UCLASS() +class ULevelList : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + + UFUNCTION(BlueprintCallable) + void CreateLevelButton(int32 index, FString MapName); + + UFUNCTION(BlueprintCallable) + void ClearLevelButtons(); + +protected: + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "LevelList", meta = (BindWidget)) + UUniformGridPanel *LevelList; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "LevelList", meta = (BindWidget)) + UButton *LevelNextPage; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "LevelList", meta = (BindWidget)) + UButton *LevelPreviousPage; + +private: + UPROPERTY(EditDefaultsOnly, Category = "LevelList") + TSubclassOf<ULevelListOption> LevelListOptionClass; +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/LevelListOption.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/LevelListOption.h new file mode 100644 index 0000000000000000000000000000000000000000..585af019e06d0cb10f5514451d2c38c2581cccd5 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/LevelListOption.h @@ -0,0 +1,36 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Blueprint/UserWidget.h" +#include "Components/TextBlock.h" +#include "Components/Button.h" +#include "LevelListOption.generated.h" + +/** + * + */ +UCLASS() +class ULevelListOption : public UUserWidget +{ + GENERATED_BODY() + +public: + virtual void NativeConstruct() override; + + void SetLevelName(FString LevelName); + + UFUNCTION(BlueprintCallable) + void OnOptionButtonClicked(); + +protected: + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "LevelListOption", meta = (BindWidget)) + UTextBlock *MapNameText; + + UPROPERTY(BlueprintReadOnly, VisibleAnywhere, Category = "LevelListOption", meta = (BindWidget)) + UButton *OptionButton; + + FString TargetLevelName; + +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/PickUpArtifact.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/PickUpArtifact.h new file mode 100644 index 0000000000000000000000000000000000000000..7133af3893f70e71a5992e861a9577f3d1634034 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/PickUpArtifact.h @@ -0,0 +1,41 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Components/StaticMeshComponent.h" +#include "PickUpArtifact.generated.h" + +UCLASS() +class APickUpArtifact : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + APickUpArtifact(); + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + +public: + // Called every frame + virtual void Tick(float DeltaTime) override; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + USceneComponent *Root; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + UStaticMeshComponent *ArtifactMeshComponent; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PickUpArtifact") + FString TargetArtifactName; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "PickUpArtifact") + UStaticMesh* TargetArtifactMesh; + + UFUNCTION(BlueprintCallable) + void SetArtifactComponentMesh(); +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/PickUpImage.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/PickUpImage.h new file mode 100644 index 0000000000000000000000000000000000000000..02a0931b72240ce7724c2c4f405e6f5682abdd3c --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/PickUpImage.h @@ -0,0 +1,43 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Components/StaticMeshComponent.h" +#include "Engine/Texture2D.h" +#include "Engine/CanvasRenderTarget2D.h" +#include "PickUpImage.generated.h" + +UCLASS() +class APickUpImage : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + APickUpImage(); + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + +public: + // Called every frame + virtual void Tick(float DeltaTime) override; + + UPROPERTY(VisibleAnywhere) + USceneComponent *Root; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + UStaticMeshComponent *ImageMeshComponent; + + UFUNCTION(BlueprintCallable) + UTexture2D *GetTexture(); + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + UTexture2D *Texture; + + UPROPERTY(EditAnywhere, BlueprintReadWrite) + UCanvasRenderTarget2D *CanvasRenderTargetOfTexture; +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/UITable.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/UITable.h new file mode 100644 index 0000000000000000000000000000000000000000..644cb9665c193092602e49c855cda44f51f7d389 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/UITable.h @@ -0,0 +1,109 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "Components/StaticMeshComponent.h" +#include "Components/WidgetComponent.h" +#include "HAL/FileManagerGeneric.h" +#include "UserMotionController.h" +#include "Components/SphereComponent.h" +#include "UITable.generated.h" + +UCLASS() +class AUITable : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AUITable(); + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + +public: + // Called every frame + virtual void Tick(float DeltaTime) override; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + UStaticMeshComponent *TableMesh; + + UPROPERTY(EditAnywhere, BluePrintReadWrite) + UStaticMeshComponent *DisplayMesh; + + UPROPERTY(VisibleAnywhere, BluePrintReadWrite) + UWidgetComponent *LevelList; + + // Options + UPROPERTY(VisibleAnywhere, BluePrintReadWrite, meta = (AllowPrivateAccess = "true")) + USphereComponent *Sphere1; + UPROPERTY(VisibleAnywhere, BluePrintReadWrite, meta = (AllowPrivateAccess = "true")) + USphereComponent *Sphere2; + UPROPERTY(VisibleAnywhere, BluePrintReadWrite, meta = (AllowPrivateAccess = "true")) + USphereComponent *Sphere3; + UPROPERTY(VisibleAnywhere, BluePrintReadWrite, meta = (AllowPrivateAccess = "true")) + USphereComponent *Sphere4; + UPROPERTY(VisibleAnywhere, BluePrintReadWrite, meta = (AllowPrivateAccess = "true")) + USphereComponent *Sphere5; + + // Pagination + UPROPERTY(VisibleAnywhere, BluePrintReadWrite, meta = (AllowPrivateAccess = "true")) + USphereComponent *PreviousPageButton; + UPROPERTY(VisibleAnywhere, BluePrintReadWrite, meta = (AllowPrivateAccess = "true")) + USphereComponent *NextPageButton; + +protected: + UFUNCTION() + void OnOptionsOverlapBegin(class UPrimitiveComponent *OverlappedComp, class AActor *OtherActor, + class UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult &SweepResult); + + UFUNCTION() + void OnPageButtonsOverlapBegin(class UPrimitiveComponent *OverlappedComp, class AActor *OtherActor, + class UPrimitiveComponent *OtherComp, int32 OtherBodyIndex, + bool bFromSweep, const FHitResult &SweepResult); + + void ChangeLevel(FString LevelName); + + void AllowOptionsOverlap(); + + void AllowPageButtonsOverlap(); + + UFUNCTION() + void GoToPreviousPage(); + + UFUNCTION() + void GoToNextPage(); + + UFUNCTION(BlueprintCallable) + void OnNextPageClicked(); + + UFUNCTION(BlueprintCallable) + void OnPreviousPageClicked(); + +private: + UPROPERTY(EditAnywhere, Category = "UITable") + int32 StartIndex = 0; + UPROPERTY(EditAnywhere, Category = "UITable") + int32 EndIndex = 4; + UPROPERTY(VisibleAnywhere) + TArray<FString> CurrentPageLevels; + + UPROPERTY(VisibleAnywhere) + bool IsOptionOverlapping = false; + + UPROPERTY(VisibleAnywhere) + bool IsPageButtonOverlapping = false; + + UPROPERTY() + TArray<FString> MapNames; + + UPROPERTY() + FString MapPath; + + UPROPERTY(VisibleAnywhere) + USceneComponent *Root; +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/UserMotionController.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/UserMotionController.h new file mode 100644 index 0000000000000000000000000000000000000000..8736302424df8fe27e3322c083e195c4dd8ca912 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/UserMotionController.h @@ -0,0 +1,26 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "GameFramework/Actor.h" +#include "UserMotionController.generated.h" + +UCLASS() +class AUserMotionController : public AActor +{ + GENERATED_BODY() + +public: + // Sets default values for this actor's properties + AUserMotionController(); + +protected: + // Called when the game starts or when spawned + virtual void BeginPlay() override; + +public: + // Called every frame + virtual void Tick(float DeltaTime) override; + +}; diff --git a/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/WarehouseFunctionLibrary.h b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/WarehouseFunctionLibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..5f36553dcf67abfa5fffa29ed25a2ca1797a1ac5 --- /dev/null +++ b/Plugins/CyberArchWarehouse/Source/CyberArchWarehouse/Public/WarehouseFunctionLibrary.h @@ -0,0 +1,42 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/StaticMesh.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "WarehouseFunctionLibrary.generated.h" + +/** + * + */ +USTRUCT(BlueprintType) +struct FTrackedData +{ + GENERATED_USTRUCT_BODY() + + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WarehouseFunctionLibrary") + FString timestamp; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WarehouseFunctionLibrary") + double xP; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WarehouseFunctionLibrary") + double yP; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WarehouseFunctionLibrary") + double xG; + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "WarehouseFunctionLibrary") + double yG; +}; + +UCLASS(Blueprintable) +class UWarehouseFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() +public: + + UFUNCTION(BlueprintCallable, Category = WarehouseFunctionLibrary) + static TArray<FTrackedData> LoadTrackedData(FString FilePath); + + UFUNCTION(BlueprintCallable, Category = WarehouseFunctionLibrary) + static UStaticMesh* LoadAssets(FString filename); +}; diff --git a/Plugins/HttpLibrary/HttpLibrary.uplugin b/Plugins/HttpLibrary/HttpLibrary.uplugin new file mode 100644 index 0000000000000000000000000000000000000000..485ed6ee45bb16f91a052cac771f3ad05c1e7385 --- /dev/null +++ b/Plugins/HttpLibrary/HttpLibrary.uplugin @@ -0,0 +1,46 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "HTTP Library", + "Description": "Send HTTP requests using blueprints.", + "Category": "Messaging", + "CreatedBy": "Tracer Interactive", + "CreatedByURL": "https://tracerinteractive.com", + "DocsURL": "https://cdn.tracerinteractive.com/httplibrary/documentation.pdf", + "MarketplaceURL": "", + "SupportURL": "", + "EngineVersion": "5.1.0", + "CanContainContent": false, + "Installed": true, + "Modules": [ + { + "Name": "HttpLibrary", + "Type": "Runtime", + "LoadingPhase": "PostConfigInit", + "WhitelistPlatforms": [ + "Win64", + "Mac", + "Linux", + "Android", + "IOS" + ] + }, + { + "Name": "HttpLibraryBlueprintSupport", + "Type": "UncookedOnly", + "LoadingPhase": "PostConfigInit", + "WhitelistPlatforms": [ + "Win64", + "Mac", + "Linux" + ] + } + ], + "Plugins": [ + { + "Name": "JsonLibrary", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/HttpLibrary.Build.cs b/Plugins/HttpLibrary/Source/HttpLibrary/HttpLibrary.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..5f3952d2930e2e0546a305909fcaef26a0fd0d07 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/HttpLibrary.Build.cs @@ -0,0 +1,20 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +namespace UnrealBuildTool.Rules +{ + public class HttpLibrary : ModuleRules + { + public HttpLibrary(ReadOnlyTargetRules Target) : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "HTTP", + "JsonLibrary" + } + ); + } + } +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryBinaryRequest.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryBinaryRequest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4e1840f154de9f0134c084471dce4413912fb84b --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryBinaryRequest.cpp @@ -0,0 +1,143 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "HttpLibraryBinaryRequest.h" +#include "HttpLibraryHelpers.h" + +void StaticBinaryProgress( FHttpRequestPtr Request, int32 BytesSent, int32 BytesReceived, FHttpLibraryProgress OnProgress ) +{ + OnProgress.ExecuteIfBound( BytesSent, BytesReceived ); +} + +void StaticBinaryResponse( FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FHttpLibraryBinaryResponse OnResponse ) +{ + if ( !OnResponse.IsBound() ) + return; + + if ( Response.IsValid() && bWasSuccessful ) + { + const int32 ResponseCode = Response->GetResponseCode(); + const TArray<FString>& ResponseHeaders = Response->GetAllHeaders(); + const TArray<uint8>& ResponseContent = Response->GetContent(); + const EHttpLibraryContentType ResponseType = UHttpLibraryHelpers::FindContentType( Response->GetContentType() ); + + TMap<FString, FString> Headers; + for ( int32 i = 0; i < ResponseHeaders.Num(); i++ ) + { + FString Key, Value; + if ( !ResponseHeaders[ i ].Split( TEXT( ": " ), &Key, &Value ) ) + continue; + + if ( !Key.IsEmpty() && Key.ToLower() != "content-type" ) + Headers.Add( Key, Value ); + } + + OnResponse.Execute( ResponseCode, Headers, ResponseType, ResponseContent ); + } + else + OnResponse.Execute( 0, TMap<FString, FString>(), EHttpLibraryContentType::Default, TArray<uint8>() ); +} + + +bool FHttpLibraryBinaryRequest::Process() +{ + HttpRequest->OnRequestProgress().BindStatic( &StaticBinaryProgress, OnProgress ); + HttpRequest->OnProcessRequestComplete().BindStatic( &StaticBinaryResponse, OnResponse ); + + return IHttpLibraryRequest::Process(); +} + + +UHttpLibraryBinaryRequest::UHttpLibraryBinaryRequest( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + Http.OnResponse.BindUObject( this, &UHttpLibraryBinaryRequest::TriggerResponse ); + Http.OnProgress.BindUObject( this, &UHttpLibraryBinaryRequest::TriggerProgress ); +} + +void UHttpLibraryBinaryRequest::TriggerResponse( int32 StatusCode, const TMap<FString, FString>& Headers, EHttpLibraryContentType Type, const TArray<uint8>& Content ) +{ + TArray<FString> Array; + for ( const TPair<FString, FString>& Temp : Headers ) + Array.Add( FString::Printf( TEXT( "%s: %s" ), *Temp.Key, *Temp.Value ) ); + + OnResponse.ExecuteIfBound( StatusCode, Array, Type, Content ); +} + +void UHttpLibraryBinaryRequest::TriggerProgress( int32 Sent, int32 Received ) +{ + OnProgress.ExecuteIfBound( Sent, Received ); +} + +bool UHttpLibraryBinaryRequest::IsRunning() const +{ + return Http.IsRunning(); +} + +bool UHttpLibraryBinaryRequest::IsComplete() const +{ + return Http.IsComplete(); +} + +bool UHttpLibraryBinaryRequest::Send( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::GET*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send(); +} + +bool UHttpLibraryBinaryRequest::SendString( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FString& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content, ContentType ); +} + +bool UHttpLibraryBinaryRequest::SendJSON( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content ); +} + +bool UHttpLibraryBinaryRequest::SendBinary( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content, ContentType ); +} + +bool UHttpLibraryBinaryRequest::Cancel() +{ + if ( !Http.IsRunning() ) + return false; + + Http.Cancel(); + return true; +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryGetRequestCallbackProxy.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryGetRequestCallbackProxy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7bcea9c676fa6026ec30fcb0f87cf199a04f9fc4 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryGetRequestCallbackProxy.cpp @@ -0,0 +1,39 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "HttpLibraryGetRequestCallbackProxy.h" +#include "HttpLibraryHelpers.h" + +UHttpLibraryGetRequestCallbackProxy::UHttpLibraryGetRequestCallbackProxy( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // +} + +void UHttpLibraryGetRequestCallbackProxy::ProcessRequest( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers ) +{ + Http.Method = EHttpLibraryRequestMethod::GET; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + Http.OnResponse.BindUObject( this, &UHttpLibraryGetRequestCallbackProxy::TriggerResponse ); + Http.Send(); +} + +void UHttpLibraryGetRequestCallbackProxy::TriggerResponse( int32 StatusCode, const FJsonLibraryValue& Content ) +{ + if ( StatusCode > 0 ) + OnSuccess.Broadcast( Content, StatusCode ); + else + OnFailure.Broadcast( FJsonLibraryValue::Parse( FString() ), 0 ); + + Http.Reset(); +} + +UHttpLibraryGetRequestCallbackProxy* UHttpLibraryGetRequestCallbackProxy::CreateProxyObjectForGet( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers ) +{ + UHttpLibraryGetRequestCallbackProxy* Proxy = NewObject<UHttpLibraryGetRequestCallbackProxy>(); + Proxy->SetFlags( RF_StrongRefOnFrame ); + Proxy->ProcessRequest( URL, QueryString, Headers ); + return Proxy; +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryHelpers.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryHelpers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d2311f4b8c2976c2097e04969d71de3b2da7d900 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryHelpers.cpp @@ -0,0 +1,196 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "HttpLibraryHelpers.h" +#include "Http.h" + +FString UHttpLibraryHelpers::GetContentType( EHttpLibraryContentType ContentType ) +{ + switch ( ContentType ) + { + case EHttpLibraryContentType::TXT: return "text/plain"; + case EHttpLibraryContentType::HTML: return "text/html"; + case EHttpLibraryContentType::CSS: return "text/css"; + case EHttpLibraryContentType::CSV: return "text/csv"; + case EHttpLibraryContentType::JSON: return "application/json"; + case EHttpLibraryContentType::JS: return "application/javascript"; + case EHttpLibraryContentType::RTF: return "application/rtf"; + case EHttpLibraryContentType::XML: return "application/xml"; + case EHttpLibraryContentType::XHTML: return "application/xhtml+xml"; + case EHttpLibraryContentType::BIN: return "application/octet-stream"; + case EHttpLibraryContentType::FORM: return "application/x-www-form-urlencoded"; + case EHttpLibraryContentType::DATA: return "multipart/form-data"; + } + + return FString(); +} + +EHttpLibraryContentType UHttpLibraryHelpers::FindContentType( const FString& ContentType ) +{ + FString LHS; + FString RHS; + if ( !ContentType.Contains( ";" ) || !ContentType.Split( ";", &LHS, &RHS ) ) + LHS = ContentType; + + LHS = LHS.ToLower(); + if ( LHS == "text/plain" ) + return EHttpLibraryContentType::TXT; + if ( LHS == "text/html" ) + return EHttpLibraryContentType::HTML; + if ( LHS == "text/css" ) + return EHttpLibraryContentType::CSS; + if ( LHS == "text/csv" ) + return EHttpLibraryContentType::CSV; + if ( LHS == "application/json" ) + return EHttpLibraryContentType::JSON; + if ( LHS == "application/javascript" ) + return EHttpLibraryContentType::JS; + if ( LHS == "application/rtf" ) + return EHttpLibraryContentType::RTF; + if ( LHS == "application/xml" ) + return EHttpLibraryContentType::XML; + if ( LHS == "application/xhtml+xml" ) + return EHttpLibraryContentType::XHTML; + if ( LHS == "application/octet-stream" ) + return EHttpLibraryContentType::BIN; + if ( LHS == "application/x-www-form-urlencoded" ) + return EHttpLibraryContentType::FORM; + if ( LHS == "multipart/form-data" ) + return EHttpLibraryContentType::DATA; + + return EHttpLibraryContentType::Default; +} + +FString UHttpLibraryHelpers::ConvertBytesToString( const TArray<uint8>& Data ) +{ + if ( Data.Num() > 0 && Data.Last() == 0 ) + return UTF8_TO_TCHAR( Data.GetData() ); + + TArray<uint8> ZeroTerminated( Data ); + ZeroTerminated.Add( 0 ); + return UTF8_TO_TCHAR( ZeroTerminated.GetData() ); +} +TArray<uint8> UHttpLibraryHelpers::ConvertStringToBytes( const FString& Data ) +{ + TArray<uint8> Payload; + + FTCHARToUTF8 Converter( *Data ); + Payload.SetNum( Converter.Length() ); + FMemory::Memcpy( Payload.GetData(), (uint8*)(ANSICHAR*)Converter.Get(), Payload.Num() ); + + return Payload; +} + +FJsonLibraryValue UHttpLibraryHelpers::ConvertBytesToJson( const TArray<uint8>& Data ) +{ + return FJsonLibraryValue::Parse( ConvertBytesToString( Data ) ); +} +TArray<uint8> UHttpLibraryHelpers::ConvertJsonToBytes( const FJsonLibraryValue& Data ) +{ + return ConvertStringToBytes( Data.Stringify() ); +} + +FString UHttpLibraryHelpers::AppendQueryString( const FString& URL, const TMap<FString, FString>& QueryString ) +{ + if ( QueryString.Num() <= 0 ) + return URL; + + FString LHS; + FString RHS; + if ( !URL.Contains( "?" ) || !URL.Split( "?", &LHS, &RHS ) ) + LHS = URL; + + for ( TPair<FString, FString> Param : QueryString ) + { + if ( Param.Key.IsEmpty() ) + continue; + + if ( !RHS.IsEmpty() ) + RHS += "&"; + + RHS += FPlatformHttp::UrlEncode( Param.Key ); + RHS += "="; + + if ( Param.Value.IsEmpty() ) + continue; + + RHS += FPlatformHttp::UrlEncode( Param.Value ); + } + + return LHS + "?" + RHS; +} + +bool UHttpLibraryHelpers::IsHttpEnabled() +{ + FHttpModule* Http = &FHttpModule::Get(); + if ( Http ) + return Http->IsHttpEnabled(); + + return false; +} + +float UHttpLibraryHelpers::GetHttpTimeout() +{ + FHttpModule* Http = &FHttpModule::Get(); + if ( !Http || !Http->IsHttpEnabled() ) + return 0.0f; + + return Http->GetHttpTimeout(); +} +void UHttpLibraryHelpers::SetHttpTimeout( float Timeout ) +{ + FHttpModule* Http = &FHttpModule::Get(); + if ( !Http || !Http->IsHttpEnabled() ) + return; + + Http->SetHttpTimeout( FMath::Max( 0.0f, Timeout ) ); +} + +UHttpLibraryRequest* UHttpLibraryHelpers::ConstructHttpRequest( const FHttpLibraryRequestOnResponse& Response ) +{ + UHttpLibraryRequest* Object = NewObject<UHttpLibraryRequest>(); + Object->OnResponse = Response; + + return Object; +} + +UHttpLibraryRequest* UHttpLibraryHelpers::ConstructHttpRequestWithProgress( const FHttpLibraryRequestOnResponse& Response, const FHttpLibraryRequestOnProgress& Progress ) +{ + UHttpLibraryRequest* Object = NewObject<UHttpLibraryRequest>(); + Object->OnResponse = Response; + Object->OnProgress = Progress; + + return Object; +} + +UHttpLibraryJsonRequest* UHttpLibraryHelpers::ConstructHttpJsonRequest( const FHttpLibraryRequestOnJsonResponse& Response ) +{ + UHttpLibraryJsonRequest* Object = NewObject<UHttpLibraryJsonRequest>(); + Object->OnResponse = Response; + + return Object; +} + +UHttpLibraryJsonRequest* UHttpLibraryHelpers::ConstructHttpJsonRequestWithProgress( const FHttpLibraryRequestOnJsonResponse& Response, const FHttpLibraryRequestOnJsonProgress& Progress ) +{ + UHttpLibraryJsonRequest* Object = NewObject<UHttpLibraryJsonRequest>(); + Object->OnResponse = Response; + Object->OnProgress = Progress; + + return Object; +} + +UHttpLibraryBinaryRequest* UHttpLibraryHelpers::ConstructHttpBinaryRequest( const FHttpLibraryRequestOnBinaryResponse& Response ) +{ + UHttpLibraryBinaryRequest* Object = NewObject<UHttpLibraryBinaryRequest>(); + Object->OnResponse = Response; + + return Object; +} + +UHttpLibraryBinaryRequest* UHttpLibraryHelpers::ConstructHttpBinaryRequestWithProgress( const FHttpLibraryRequestOnBinaryResponse& Response, const FHttpLibraryRequestOnBinaryProgress& Progress ) +{ + UHttpLibraryBinaryRequest* Object = NewObject<UHttpLibraryBinaryRequest>(); + Object->OnResponse = Response; + Object->OnProgress = Progress; + + return Object; +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryJsonRequest.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryJsonRequest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25cf69b160c3379d4205b4c77f8c8e8c6b477f24 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryJsonRequest.cpp @@ -0,0 +1,139 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "HttpLibraryJsonRequest.h" +#include "HttpLibraryHelpers.h" + +void StaticJsonProgress( FHttpRequestPtr Request, int32 BytesSent, int32 BytesReceived, FHttpLibraryProgress OnProgress ) +{ + OnProgress.ExecuteIfBound( BytesSent, BytesReceived ); +} + +void StaticJsonResponse( FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FHttpLibraryJsonResponse OnResponse ) +{ + if ( !OnResponse.IsBound() ) + return; + + if ( Response.IsValid() && bWasSuccessful ) + { + const int32 ResponseCode = Response->GetResponseCode(); + const TArray<uint8>& ResponseContent = Response->GetContent(); + const EHttpLibraryContentType ResponseType = UHttpLibraryHelpers::FindContentType( Response->GetContentType() ); + + switch ( ResponseType ) + { + case EHttpLibraryContentType::Default: + case EHttpLibraryContentType::JSON: + case EHttpLibraryContentType::JS: + case EHttpLibraryContentType::TXT: + OnResponse.Execute( ResponseCode, UHttpLibraryHelpers::ConvertBytesToJson( ResponseContent ) ); + break; + + default: + OnResponse.Execute( ResponseCode, FJsonLibraryValue::Parse( FString() ) ); + break; + } + } + else + OnResponse.Execute( 0, FJsonLibraryValue::Parse( FString() ) ); +} + + +bool FHttpLibraryJsonRequest::Process() +{ + HttpRequest->OnRequestProgress().BindStatic( &StaticJsonProgress, OnProgress ); + HttpRequest->OnProcessRequestComplete().BindStatic( &StaticJsonResponse, OnResponse ); + + return IHttpLibraryRequest::Process(); +} + + +UHttpLibraryJsonRequest::UHttpLibraryJsonRequest( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + Http.OnResponse.BindUObject( this, &UHttpLibraryJsonRequest::TriggerResponse ); + Http.OnProgress.BindUObject( this, &UHttpLibraryJsonRequest::TriggerProgress ); +} + +void UHttpLibraryJsonRequest::TriggerResponse( int32 StatusCode, const FJsonLibraryValue& Content ) +{ + OnResponse.ExecuteIfBound( StatusCode, Content ); +} + +void UHttpLibraryJsonRequest::TriggerProgress( int32 Sent, int32 Received ) +{ + OnProgress.ExecuteIfBound( Sent, Received ); +} + +bool UHttpLibraryJsonRequest::IsRunning() const +{ + return Http.IsRunning(); +} + +bool UHttpLibraryJsonRequest::IsComplete() const +{ + return Http.IsComplete(); +} + +bool UHttpLibraryJsonRequest::Send( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::GET*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send(); +} + +bool UHttpLibraryJsonRequest::SendString( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FString& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content, ContentType ); +} + +bool UHttpLibraryJsonRequest::SendJSON( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content ); +} + +bool UHttpLibraryJsonRequest::SendBinary( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content, ContentType ); +} + +bool UHttpLibraryJsonRequest::Cancel() +{ + if ( !Http.IsRunning() ) + return false; + + Http.Cancel(); + return true; +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryModule.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7f6f1960a034c0cf9c5037d4fd006bf01d0db84d --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryModule.cpp @@ -0,0 +1,19 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "HttpLibraryModule.h" +#include "Modules/ModuleManager.h" + +class FHttpLibraryModule : public IHttpLibraryModule +{ +public: + virtual void StartupModule() override + { + // + } + + virtual void ShutdownModule() override + { + // + } +}; + +IMPLEMENT_MODULE(FHttpLibraryModule, HttpLibrary); diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryPostRequestCallbackProxy.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryPostRequestCallbackProxy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9681cc68c111373591aaaf9fe489f7dbc2f0aa26 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryPostRequestCallbackProxy.cpp @@ -0,0 +1,39 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "HttpLibraryPostRequestCallbackProxy.h" +#include "HttpLibraryHelpers.h" + +UHttpLibraryPostRequestCallbackProxy::UHttpLibraryPostRequestCallbackProxy( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // +} + +void UHttpLibraryPostRequestCallbackProxy::ProcessRequest( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content ) +{ + Http.Method = EHttpLibraryRequestMethod::POST; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + Http.OnResponse.BindUObject( this, &UHttpLibraryPostRequestCallbackProxy::TriggerResponse ); + Http.Send( Content ); +} + +void UHttpLibraryPostRequestCallbackProxy::TriggerResponse( int32 StatusCode, const FJsonLibraryValue& Content ) +{ + if ( StatusCode > 0 ) + OnSuccess.Broadcast( Content, StatusCode ); + else + OnFailure.Broadcast( FJsonLibraryValue::Parse( FString() ), 0 ); + + Http.Reset(); +} + +UHttpLibraryPostRequestCallbackProxy* UHttpLibraryPostRequestCallbackProxy::CreateProxyObjectForPost( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content ) +{ + UHttpLibraryPostRequestCallbackProxy* Proxy = NewObject<UHttpLibraryPostRequestCallbackProxy>(); + Proxy->SetFlags( RF_StrongRefOnFrame ); + Proxy->ProcessRequest( URL, QueryString, Headers, Content ); + return Proxy; +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryRequest.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryRequest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfdd8c40f305bc2ef2d253ca44c666df643f48c8 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryRequest.cpp @@ -0,0 +1,126 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "HttpLibraryRequest.h" +#include "HttpLibraryHelpers.h" + +void StaticProgress( FHttpRequestPtr Request, int32 BytesSent, int32 BytesReceived, FHttpLibraryProgress OnProgress ) +{ + OnProgress.ExecuteIfBound( BytesSent, BytesReceived ); +} + +void StaticResponse( FHttpRequestPtr Request, FHttpResponsePtr Response, bool bWasSuccessful, FHttpLibraryResponse OnResponse ) +{ + if ( !OnResponse.IsBound() ) + return; + + if ( Response.IsValid() && bWasSuccessful ) + { + const int32 ResponseCode = Response->GetResponseCode(); + const TArray<uint8>& ResponseContent = Response->GetContent(); + const EHttpLibraryContentType ResponseType = UHttpLibraryHelpers::FindContentType( Response->GetContentType() ); + + OnResponse.Execute( ResponseCode, ResponseType, UHttpLibraryHelpers::ConvertBytesToString( ResponseContent ) ); + } + else + OnResponse.Execute( 0, EHttpLibraryContentType::Default, FString() ); +} + +bool FHttpLibraryRequest::Process() +{ + HttpRequest->OnRequestProgress().BindStatic( &StaticProgress, OnProgress ); + HttpRequest->OnProcessRequestComplete().BindStatic( &StaticResponse, OnResponse ); + + return IHttpLibraryRequest::Process(); +} + + +UHttpLibraryRequest::UHttpLibraryRequest( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + Http.OnResponse.BindUObject( this, &UHttpLibraryRequest::TriggerResponse ); + Http.OnProgress.BindUObject( this, &UHttpLibraryRequest::TriggerProgress ); +} + +void UHttpLibraryRequest::TriggerResponse( int32 StatusCode, EHttpLibraryContentType Type, const FString& Content ) +{ + OnResponse.ExecuteIfBound( StatusCode, Type, Content ); +} + +void UHttpLibraryRequest::TriggerProgress( int32 Sent, int32 Received ) +{ + OnProgress.ExecuteIfBound( Sent, Received ); +} + +bool UHttpLibraryRequest::IsRunning() const +{ + return Http.IsRunning(); +} + +bool UHttpLibraryRequest::IsComplete() const +{ + return Http.IsComplete(); +} + +bool UHttpLibraryRequest::Send( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::GET*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send(); +} + +bool UHttpLibraryRequest::SendString( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FString& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content, ContentType ); +} + +bool UHttpLibraryRequest::SendJSON( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content ); +} + +bool UHttpLibraryRequest::SendBinary( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/, EHttpLibraryRequestMethod Method /*= EHttpLibraryRequestMethod::POST*/ ) +{ + if ( Http.IsRunning() ) + return false; + + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + return Http.Send( Content, ContentType ); +} + +bool UHttpLibraryRequest::Cancel() +{ + if ( !Http.IsRunning() ) + return false; + + Http.Cancel(); + return true; +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryRequestCallbackProxy.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryRequestCallbackProxy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d79403bb38a67a51f588beb89d87fea76491675a --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/HttpLibraryRequestCallbackProxy.cpp @@ -0,0 +1,53 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "HttpLibraryRequestCallbackProxy.h" +#include "HttpLibraryHelpers.h" + +UHttpLibraryRequestCallbackProxy::UHttpLibraryRequestCallbackProxy( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + HttpSent = 0; + HttpReceived = 0; +} + +void UHttpLibraryRequestCallbackProxy::ProcessRequest( EHttpLibraryRequestMethod Method, const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType ) +{ + Http.Method = Method; + Http.URL = URL; + + Http.QueryString = QueryString; + Http.Headers = Headers; + + Http.OnResponse.BindUObject( this, &UHttpLibraryRequestCallbackProxy::TriggerResponse ); + Http.OnProgress.BindUObject( this, &UHttpLibraryRequestCallbackProxy::TriggerProgress ); + + Http.Send( Content, ContentType ); +} + +void UHttpLibraryRequestCallbackProxy::TriggerResponse( int32 StatusCode, const TMap<FString, FString>& Headers, EHttpLibraryContentType ContentType, const TArray<uint8>& Content ) +{ + if ( StatusCode > 0 ) + OnSuccess.Broadcast( Content, ContentType, StatusCode, HttpSent, HttpReceived ); + else + OnFailure.Broadcast( TArray<uint8>(), EHttpLibraryContentType::Default, 0, HttpSent, HttpReceived ); + + HttpSent = 0; + HttpReceived = 0; + + Http.Reset(); +} + +void UHttpLibraryRequestCallbackProxy::TriggerProgress( int32 Sent, int32 Received ) +{ + HttpSent = Sent; + HttpReceived = Received; + + OnProgress.Broadcast( TArray<uint8>(), EHttpLibraryContentType::Default, 0, HttpSent, HttpReceived ); +} + +UHttpLibraryRequestCallbackProxy* UHttpLibraryRequestCallbackProxy::CreateProxyObjectForRequest( EHttpLibraryRequestMethod Method, const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType ) +{ + UHttpLibraryRequestCallbackProxy* Proxy = NewObject<UHttpLibraryRequestCallbackProxy>(); + Proxy->SetFlags( RF_StrongRefOnFrame ); + Proxy->ProcessRequest( Method, URL, QueryString, Headers, Content, ContentType ); + return Proxy; +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Private/IHttpLibraryRequest.cpp b/Plugins/HttpLibrary/Source/HttpLibrary/Private/IHttpLibraryRequest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..27c292a568b289d0f70c40d8ec0a2c4eac766e18 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Private/IHttpLibraryRequest.cpp @@ -0,0 +1,154 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "IHttpLibraryRequest.h" +#include "HttpLibraryHelpers.h" + +IHttpLibraryRequest::IHttpLibraryRequest() +{ + Method = EHttpLibraryRequestMethod::GET; +} + +IHttpLibraryRequest::~IHttpLibraryRequest() +{ + HttpRequest.Reset(); +} + +bool IHttpLibraryRequest::Create() +{ + return Create( TArray<uint8>(), EHttpLibraryContentType::Default ); +} + +bool IHttpLibraryRequest::Create( const FString& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/ ) +{ + return Create( UHttpLibraryHelpers::ConvertStringToBytes( Content ), ContentType ); +} + +bool IHttpLibraryRequest::Create( const TArray<uint8>& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/ ) +{ + if ( HttpRequest.IsValid() ) + HttpRequest.Reset(); + + if ( URL.IsEmpty() ) + return false; + + FHttpModule* Http = &FHttpModule::Get(); + if ( !Http || !Http->IsHttpEnabled() ) + return false; + + HttpRequest = Http->CreateRequest(); + if ( !HttpRequest.IsValid() ) + return false; + + switch ( Method ) + { + default: + case EHttpLibraryRequestMethod::GET: HttpRequest->SetVerb( "GET" ); break; + case EHttpLibraryRequestMethod::POST: HttpRequest->SetVerb( "POST" ); break; + case EHttpLibraryRequestMethod::PUT: HttpRequest->SetVerb( "PUT" ); break; + case EHttpLibraryRequestMethod::PATCH: HttpRequest->SetVerb( "PATCH" ); break; + case EHttpLibraryRequestMethod::DELETE: HttpRequest->SetVerb( "DELETE" ); break; + case EHttpLibraryRequestMethod::HEAD: HttpRequest->SetVerb( "HEAD" ); break; + case EHttpLibraryRequestMethod::CONNECT: HttpRequest->SetVerb( "CONNECT" ); break; + case EHttpLibraryRequestMethod::OPTIONS: HttpRequest->SetVerb( "OPTIONS" ); break; + case EHttpLibraryRequestMethod::TRACE: HttpRequest->SetVerb( "TRACE" ); break; + } + + if ( QueryString.Num() > 0 ) + HttpRequest->SetURL( UHttpLibraryHelpers::AppendQueryString( URL, QueryString ) ); + else + HttpRequest->SetURL( URL ); + + if ( ContentType != EHttpLibraryContentType::Default ) + HttpRequest->SetHeader( "Content-Type", UHttpLibraryHelpers::GetContentType( ContentType ) ); + + if ( Headers.Num() > 0 ) + for ( TPair<FString, FString> Header : Headers ) + if ( Header.Key.ToLower() != "content-type" ) + HttpRequest->SetHeader( Header.Key, Header.Value ); + + if ( Content.Num() > 0 ) + HttpRequest->SetContent( Content ); + + return true; +} + +bool IHttpLibraryRequest::Create( const FJsonLibraryValue& Content ) +{ + return Create( UHttpLibraryHelpers::ConvertJsonToBytes( Content ), EHttpLibraryContentType::JSON ); +} + +bool IHttpLibraryRequest::Process() +{ + return HttpRequest->ProcessRequest(); +} + +bool IHttpLibraryRequest::IsRunning() const +{ + if ( !HttpRequest.IsValid() ) + return false; + + EHttpRequestStatus::Type Status = HttpRequest->GetStatus(); + return Status == EHttpRequestStatus::Processing; +} + +bool IHttpLibraryRequest::IsComplete() const +{ + if ( !HttpRequest.IsValid() ) + return false; + + EHttpRequestStatus::Type Status = HttpRequest->GetStatus(); + return Status == EHttpRequestStatus::Failed || Status == EHttpRequestStatus::Failed_ConnectionError || Status == EHttpRequestStatus::Succeeded; +} + +bool IHttpLibraryRequest::Send() +{ + if ( !Create() ) + return false; + + return Process(); +} + +bool IHttpLibraryRequest::Send( const FString& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/ ) +{ + if ( !Create( Content, ContentType ) ) + return false; + + return Process(); +} + +bool IHttpLibraryRequest::Send( const TArray<uint8>& Content, EHttpLibraryContentType ContentType /*= EHttpLibraryContentType::Default*/ ) +{ + if ( !Create( Content, ContentType ) ) + return false; + + return Process(); +} + +bool IHttpLibraryRequest::Send( const FJsonLibraryValue& Content ) +{ + if ( !Create( Content ) ) + return false; + + return Process(); +} + +void IHttpLibraryRequest::Cancel() +{ + if ( !HttpRequest.IsValid() ) + return; + + if ( HttpRequest->GetStatus() == EHttpRequestStatus::Processing ) + HttpRequest->CancelRequest(); + + HttpRequest.Reset(); +} + +void IHttpLibraryRequest::Reset() +{ + Method = EHttpLibraryRequestMethod::GET; + URL = FString(); + + QueryString = TMap<FString, FString>(); + Headers = TMap<FString, FString>(); + + HttpRequest.Reset(); +} diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibrary.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..c1dd921df26f8c59c070c0aae6e9867f27a4d292 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibrary.h @@ -0,0 +1,7 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "HttpLibraryEnums.h" +#include "HttpLibraryRequest.h" +#include "HttpLibraryJsonRequest.h" +#include "HttpLibraryBinaryRequest.h" +#include "HttpLibraryHelpers.h" diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryBinaryRequest.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryBinaryRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..df846ea86a9b060cffba93ccb750e9f8745ce6de --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryBinaryRequest.h @@ -0,0 +1,65 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "IHttpLibraryRequest.h" +#include "CoreMinimal.h" +#include "Http.h" +#include "HttpLibraryEnums.h" +#include "JsonLibrary.h" +#include "HttpLibraryBinaryRequest.generated.h" + +struct HTTPLIBRARY_API FHttpLibraryBinaryRequest : public IHttpLibraryRequest +{ + FHttpLibraryBinaryResponse OnResponse; + FHttpLibraryProgress OnProgress; + +protected: + + virtual bool Process() override; +}; + +DECLARE_DYNAMIC_DELEGATE_FourParams( FHttpLibraryRequestOnBinaryResponse, int32, StatusCode, const TArray<FString>&, Headers, EHttpLibraryContentType, ContentType, const TArray<uint8>&, Content ); +DECLARE_DYNAMIC_DELEGATE_TwoParams( FHttpLibraryRequestOnBinaryProgress, int32, BytesSent, int32, BytesReceived ); + +UCLASS(BlueprintType, meta = (DisplayName = "HTTP Request (Binary)")) +class HTTPLIBRARY_API UHttpLibraryBinaryRequest : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + FHttpLibraryRequestOnBinaryResponse OnResponse; + FHttpLibraryRequestOnBinaryProgress OnProgress; + +protected: + + FHttpLibraryBinaryRequest Http; + + void TriggerResponse( int32 StatusCode, const TMap<FString, FString>& Headers, EHttpLibraryContentType ContentType, const TArray<uint8>& Content ); + void TriggerProgress( int32 Sent, int32 Received ); + +public: + + // Check if a HTTP request is in progress. + UFUNCTION(BlueprintPure, meta = (DisplayName = "In Progress"), Category = "HTTP Library|Request") + bool IsRunning() const; + // Check if a HTTP request is complete. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Complete"), Category = "HTTP Library|Request") + bool IsComplete() const; + + // Send a HTTP request. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Request", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers"), Category = "HTTP Library|Request") + bool Send( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::GET ); + // Send a HTTP request with content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers"), Category = "HTTP Library|Request") + bool SendString( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FString& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + // Send a HTTP request with JSON content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content (JSON)", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers,Content"), Category = "HTTP Library|Request") + bool SendJSON( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + // Send a HTTP request with binary content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content (Binary)", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers,Content"), Category = "HTTP Library|Request") + bool SendBinary( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + + // Cancel a HTTP request if currently in progress. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Cancel"), Category = "HTTP Library|Request") + bool Cancel(); +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryEnums.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryEnums.h new file mode 100644 index 0000000000000000000000000000000000000000..86375167226c625cc2daea47ea8ec1cb5ba3955e --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryEnums.h @@ -0,0 +1,35 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "HttpLibraryEnums.generated.h" + +UENUM(BlueprintType, meta = (DisplayName = "Request Method")) +enum class EHttpLibraryRequestMethod : uint8 +{ + GET UMETA(DisplayName="GET"), + POST UMETA(DisplayName="POST"), + PUT UMETA(DisplayName="PUT"), + PATCH UMETA(DisplayName="PATCH"), + DELETE UMETA(DisplayName="DELETE"), + HEAD UMETA(DisplayName="HEAD"), + CONNECT UMETA(DisplayName="CONNECT"), + OPTIONS UMETA(DisplayName="OPTIONS"), + TRACE UMETA(DisplayName="TRACE") +}; + +UENUM(BlueprintType, meta = (DisplayName = "Content Type")) +enum class EHttpLibraryContentType : uint8 +{ + Default UMETA(DisplayName="*"), + TXT UMETA(DisplayName="text/plain"), + HTML UMETA(DisplayName="text/html"), + CSS UMETA(DisplayName="text/css"), + CSV UMETA(DisplayName="text/csv"), + JSON UMETA(DisplayName="application/json"), + JS UMETA(DisplayName="application/javascript"), + RTF UMETA(DisplayName="application/rtf"), + XML UMETA(DisplayName="application/xml"), + XHTML UMETA(DisplayName="application/xhtml+xml"), + BIN UMETA(DisplayName="application/octet-stream"), + FORM UMETA(DisplayName="application/x-www-form-urlencoded"), + DATA UMETA(DisplayName="multipart/form-data") +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryGetRequestCallbackProxy.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryGetRequestCallbackProxy.h new file mode 100644 index 0000000000000000000000000000000000000000..7fb1a90660bec2038adfd12d9d5daaf14f28a020 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryGetRequestCallbackProxy.h @@ -0,0 +1,32 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Http.h" +#include "HttpLibraryEnums.h" +#include "HttpLibraryJsonRequest.h" +#include "JsonLibrary.h" +#include "HttpLibraryGetRequestCallbackProxy.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FHttpLibraryGetRequestCallback, const FJsonLibraryValue&, Response, int32, StatusCode ); + +UCLASS(MinimalAPI) +class UHttpLibraryGetRequestCallbackProxy : public UObject +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(BlueprintAssignable) + FHttpLibraryGetRequestCallback OnSuccess; + + UPROPERTY(BlueprintAssignable) + FHttpLibraryGetRequestCallback OnFailure; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "HTTP GET Request Proxy", AdvancedDisplay = "QueryString,Headers", AutoCreateRefTerm = "QueryString,Headers"), Category = "HTTP Library") + static UHttpLibraryGetRequestCallbackProxy* CreateProxyObjectForGet( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers ); + +protected: + + FHttpLibraryJsonRequest Http; + + void ProcessRequest( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers ); + void TriggerResponse( int32 StatusCode, const FJsonLibraryValue& Content ); +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryHelpers.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryHelpers.h new file mode 100644 index 0000000000000000000000000000000000000000..91f78d6f35935cc93d92710af61c7811b2790ab8 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryHelpers.h @@ -0,0 +1,79 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "HttpLibraryEnums.h" +#include "HttpLibraryRequest.h" +#include "HttpLibraryBinaryRequest.h" +#include "HttpLibraryJsonRequest.h" +#include "JsonLibrary.h" +#include "HttpLibraryHelpers.generated.h" + +UCLASS() +class HTTPLIBRARY_API UHttpLibraryHelpers : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + + // Get the content type string. + UFUNCTION(BlueprintPure, Category = "HTTP Library|Helpers") + static FString GetContentType( EHttpLibraryContentType ContentType ); + // Find the content type from a string. + UFUNCTION(BlueprintPure, Category = "HTTP Library|Helpers") + static EHttpLibraryContentType FindContentType( const FString& ContentType ); + + // Append a query string to a URL. + UFUNCTION(BlueprintCallable, Category = "HTTP Library|Helpers", meta = (AutoCreateRefTerm = "QueryString")) + static FString AppendQueryString( const FString& URL, const TMap<FString, FString>& QueryString ); + + // Convert an array of bytes to a string. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Convert Bytes to String"), Category = "HTTP Library|Helpers", meta = (AutoCreateRefTerm = "Data")) + static FString ConvertBytesToString( const TArray<uint8>& Data ); + // Convert a string to an array of bytes. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Convert String to Bytes"), Category = "HTTP Library|Helpers") + static TArray<uint8> ConvertStringToBytes( const FString& Data ); + // Convert an array of bytes to a string. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Convert Bytes to JSON"), Category = "HTTP Library|Helpers", meta = (AutoCreateRefTerm = "Data")) + static FJsonLibraryValue ConvertBytesToJson( const TArray<uint8>& Data ); + // Convert a string to an array of bytes. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Convert JSON to Bytes"), Category = "HTTP Library|Helpers") + static TArray<uint8> ConvertJsonToBytes( const FJsonLibraryValue& Data ); + + // Check if HTTP is enabled. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Is HTTP Enabled"), Category = "HTTP Library|Settings") + static bool IsHttpEnabled(); + // Get the default HTTP timeout. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get HTTP Timeout"), Category = "HTTP Library|Settings") + static float GetHttpTimeout(); + // Set the default HTTP timeout. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set HTTP Timeout"), Category = "HTTP Library|Settings") + static void SetHttpTimeout( float Timeout ); + +#if UE_EDITOR +protected: +#else +public: +#endif + + // Construct a HTTP request object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct HTTP", CompactNodeTitle = "HTTP", AutoCreateRefTerm = "Response"), Category = "HTTP Library|Request") + static UHttpLibraryRequest* ConstructHttpRequest( const FHttpLibraryRequestOnResponse& Response ); + // Construct a HTTP request object with progress updates. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct HTTP w/ Progress", AutoCreateRefTerm = "Response,Progress"), Category = "HTTP Library|Request|Progress") + static UHttpLibraryRequest* ConstructHttpRequestWithProgress( const FHttpLibraryRequestOnResponse& Response, const FHttpLibraryRequestOnProgress& Progress ); + + // Construct a HTTP JSON request object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct HTTP (JSON)", CompactNodeTitle = "HTTP", AutoCreateRefTerm = "Response"), Category = "HTTP Library|Request") + static UHttpLibraryJsonRequest* ConstructHttpJsonRequest( const FHttpLibraryRequestOnJsonResponse& Response ); + // Construct a HTTP JSON request object with progress updates. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct HTTP w/ Progress (JSON)", AutoCreateRefTerm = "Response,Progress"), Category = "HTTP Library|Request|Progress") + static UHttpLibraryJsonRequest* ConstructHttpJsonRequestWithProgress( const FHttpLibraryRequestOnJsonResponse& Response, const FHttpLibraryRequestOnJsonProgress& Progress ); + + // Construct a HTTP binary request object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct HTTP (Binary)", CompactNodeTitle = "HTTP", AutoCreateRefTerm = "Response"), Category = "HTTP Library|Request") + static UHttpLibraryBinaryRequest* ConstructHttpBinaryRequest( const FHttpLibraryRequestOnBinaryResponse& Response ); + // Construct a HTTP binary request object with progress updates. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct HTTP w/ Progress (Binary)", AutoCreateRefTerm = "Response,Progress"), Category = "HTTP Library|Request|Progress") + static UHttpLibraryBinaryRequest* ConstructHttpBinaryRequestWithProgress( const FHttpLibraryRequestOnBinaryResponse& Response, const FHttpLibraryRequestOnBinaryProgress& Progress ); +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryJsonRequest.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryJsonRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..8fbf7c27387022c81d78e64c27f669eb82a57916 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryJsonRequest.h @@ -0,0 +1,65 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "IHttpLibraryRequest.h" +#include "CoreMinimal.h" +#include "Http.h" +#include "HttpLibraryEnums.h" +#include "JsonLibrary.h" +#include "HttpLibraryJsonRequest.generated.h" + +struct HTTPLIBRARY_API FHttpLibraryJsonRequest : IHttpLibraryRequest +{ + FHttpLibraryJsonResponse OnResponse; + FHttpLibraryProgress OnProgress; + +protected: + + virtual bool Process() override; +}; + +DECLARE_DYNAMIC_DELEGATE_TwoParams( FHttpLibraryRequestOnJsonResponse, int32, StatusCode, const FJsonLibraryValue&, Content ); +DECLARE_DYNAMIC_DELEGATE_TwoParams( FHttpLibraryRequestOnJsonProgress, int32, BytesSent, int32, BytesReceived ); + +UCLASS(BlueprintType, meta = (DisplayName = "HTTP Request (JSON)")) +class HTTPLIBRARY_API UHttpLibraryJsonRequest : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + FHttpLibraryRequestOnJsonResponse OnResponse; + FHttpLibraryRequestOnJsonProgress OnProgress; + +protected: + + FHttpLibraryJsonRequest Http; + + void TriggerResponse( int32 StatusCode, const FJsonLibraryValue& Content ); + void TriggerProgress( int32 Sent, int32 Received ); + +public: + + // Check if a HTTP request is in progress. + UFUNCTION(BlueprintPure, meta = (DisplayName = "In Progress"), Category = "HTTP Library|Request") + bool IsRunning() const; + // Check if a HTTP request is complete. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Complete"), Category = "HTTP Library|Request") + bool IsComplete() const; + + // Send a HTTP request. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Request", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers"), Category = "HTTP Library|Request") + bool Send( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::GET ); + // Send a HTTP request with content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers"), Category = "HTTP Library|Request") + bool SendString( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FString& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + // Send a HTTP request with JSON content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content (JSON)", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers,Content"), Category = "HTTP Library|Request") + bool SendJSON( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + // Send a HTTP request with binary content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content (Binary)", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers,Content"), Category = "HTTP Library|Request") + bool SendBinary( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + + // Cancel a HTTP request if currently in progress. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Cancel"), Category = "HTTP Library|Request") + bool Cancel(); +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryModule.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryModule.h new file mode 100644 index 0000000000000000000000000000000000000000..7e124d46e19f75e5dd3298240d6278bda5e94fd6 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryModule.h @@ -0,0 +1,18 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "Modules/ModuleManager.h" + +class IHttpLibraryModule : public IModuleInterface +{ +public: + + static inline IHttpLibraryModule& Get() + { + return FModuleManager::LoadModuleChecked<IHttpLibraryModule>( "HttpLibrary" ); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "HttpLibrary" ); + } +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryPostRequestCallbackProxy.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryPostRequestCallbackProxy.h new file mode 100644 index 0000000000000000000000000000000000000000..275addf23ea9220be3cef2ac77dd0b9869238c3c --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryPostRequestCallbackProxy.h @@ -0,0 +1,32 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Http.h" +#include "HttpLibraryEnums.h" +#include "HttpLibraryJsonRequest.h" +#include "JsonLibrary.h" +#include "HttpLibraryPostRequestCallbackProxy.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FHttpLibraryPostRequestCallback, const FJsonLibraryValue&, Response, int32, StatusCode ); + +UCLASS(MinimalAPI) +class UHttpLibraryPostRequestCallbackProxy : public UObject +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(BlueprintAssignable) + FHttpLibraryPostRequestCallback OnSuccess; + + UPROPERTY(BlueprintAssignable) + FHttpLibraryPostRequestCallback OnFailure; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "HTTP POST Request Proxy", AdvancedDisplay = "QueryString,Headers", AutoCreateRefTerm = "QueryString,Headers,Content"), Category = "HTTP Library") + static UHttpLibraryPostRequestCallbackProxy* CreateProxyObjectForPost( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content ); + +protected: + + FHttpLibraryJsonRequest Http; + + void ProcessRequest( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content ); + void TriggerResponse( int32 StatusCode, const FJsonLibraryValue& Content ); +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryRequest.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..608fab5e1f207b8069ee742ea42fd20843dd7b92 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryRequest.h @@ -0,0 +1,65 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "IHttpLibraryRequest.h" +#include "CoreMinimal.h" +#include "Http.h" +#include "HttpLibraryEnums.h" +#include "JsonLibrary.h" +#include "HttpLibraryRequest.generated.h" + +struct HTTPLIBRARY_API FHttpLibraryRequest : IHttpLibraryRequest +{ + FHttpLibraryResponse OnResponse; + FHttpLibraryProgress OnProgress; + +protected: + + virtual bool Process() override; +}; + +DECLARE_DYNAMIC_DELEGATE_ThreeParams( FHttpLibraryRequestOnResponse, int32, StatusCode, EHttpLibraryContentType, ContentType, const FString&, Content ); +DECLARE_DYNAMIC_DELEGATE_TwoParams( FHttpLibraryRequestOnProgress, int32, BytesSent, int32, BytesReceived ); + +UCLASS(BlueprintType, meta = (DisplayName = "HTTP Request")) +class HTTPLIBRARY_API UHttpLibraryRequest : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + FHttpLibraryRequestOnResponse OnResponse; + FHttpLibraryRequestOnProgress OnProgress; + +protected: + + FHttpLibraryRequest Http; + + void TriggerResponse( int32 StatusCode, EHttpLibraryContentType Type, const FString& Content ); + void TriggerProgress( int32 Sent, int32 Received ); + +public: + + // Check if a HTTP request is in progress. + UFUNCTION(BlueprintPure, meta = (DisplayName = "In Progress"), Category = "HTTP Library|Request") + bool IsRunning() const; + // Check if a HTTP request is complete. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Is Complete"), Category = "HTTP Library|Request") + bool IsComplete() const; + + // Send a HTTP request. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Request", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers"), Category = "HTTP Library|Request") + bool Send( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::GET ); + // Send a HTTP request with content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers"), Category = "HTTP Library|Request") + bool SendString( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FString& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + // Send a HTTP request with JSON content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content (JSON)", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers,Content"), Category = "HTTP Library|Request") + bool SendJSON( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const FJsonLibraryValue& Content, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + // Send a HTTP request with binary content. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Send Content (Binary)", AdvancedDisplay = "QueryString,Headers,Method", AutoCreateRefTerm = "QueryString,Headers,Content"), Category = "HTTP Library|Request") + bool SendBinary( const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default, EHttpLibraryRequestMethod Method = EHttpLibraryRequestMethod::POST ); + + // Cancel a HTTP request if currently in progress. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Cancel"), Category = "HTTP Library|Request") + bool Cancel(); +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryRequestCallbackProxy.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryRequestCallbackProxy.h new file mode 100644 index 0000000000000000000000000000000000000000..2c05905a0e40a07a087026f6b86d23274ca17f28 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/HttpLibraryRequestCallbackProxy.h @@ -0,0 +1,41 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Http.h" +#include "HttpLibraryEnums.h" +#include "HttpLibraryBinaryRequest.h" +#include "HttpLibraryRequestCallbackProxy.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_FiveParams( FHttpLibraryRequestCallback, const TArray<uint8>&, Response, EHttpLibraryContentType, ContentType, int32, StatusCode, int32, BytesSent, int32, BytesReceived ); + +UCLASS(MinimalAPI) +class UHttpLibraryRequestCallbackProxy : public UObject +{ + GENERATED_UCLASS_BODY() + + UPROPERTY(BlueprintAssignable) + FHttpLibraryRequestCallback OnSuccess; + + UPROPERTY(BlueprintAssignable) + FHttpLibraryRequestCallback OnProgress; + + UPROPERTY(BlueprintAssignable) + FHttpLibraryRequestCallback OnFailure; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true", DisplayName = "HTTP Request Proxy", AdvancedDisplay = "Method,QueryString,Headers,Content,ContentType", AutoCreateRefTerm = "QueryString,Headers,Content"), Category = "HTTP Library") + static UHttpLibraryRequestCallbackProxy* CreateProxyObjectForRequest( EHttpLibraryRequestMethod Method, const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default ); + +protected: + + FHttpLibraryBinaryRequest Http; + + void TriggerResponse( int32 StatusCode, const TMap<FString, FString>& Headers, EHttpLibraryContentType ContentType, const TArray<uint8>& Content ); + void TriggerProgress( int32 Sent, int32 Received ); + + void ProcessRequest( EHttpLibraryRequestMethod Method, const FString& URL, const TMap<FString, FString>& QueryString, const TMap<FString, FString>& Headers, const TArray<uint8>& Content, EHttpLibraryContentType ContentType ); + +private: + + int32 HttpSent; + int32 HttpReceived; +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibrary/Public/IHttpLibraryRequest.h b/Plugins/HttpLibrary/Source/HttpLibrary/Public/IHttpLibraryRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..b0c12d174391c979e4a5306f3c293187e2960490 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibrary/Public/IHttpLibraryRequest.h @@ -0,0 +1,50 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Http.h" +#include "HttpLibraryEnums.h" +#include "JsonLibrary.h" + +typedef TMap<FString, FString> FHttpLibraryHeaders; + +DECLARE_DELEGATE_ThreeParams( FHttpLibraryResponse, int32 /*StatusCode*/, EHttpLibraryContentType /*ContentType*/, const FString& /*Content*/ ); +DECLARE_DELEGATE_TwoParams( FHttpLibraryProgress, int32 /*Sent*/, int32 /*Received*/ ); + +DECLARE_DELEGATE_TwoParams( FHttpLibraryJsonResponse, int32 /*StatusCode*/, const FJsonLibraryValue& /*Content*/ ); +DECLARE_DELEGATE_FourParams( FHttpLibraryBinaryResponse, int32 /*StatusCode*/, const FHttpLibraryHeaders& /*Headers*/, EHttpLibraryContentType /*ContentType*/, const TArray<uint8>& /*Content*/ ); + +struct HTTPLIBRARY_API IHttpLibraryRequest +{ + IHttpLibraryRequest(); + virtual ~IHttpLibraryRequest(); + + EHttpLibraryRequestMethod Method; + FString URL; + + TMap<FString, FString> Headers; + TMap<FString, FString> QueryString; + +protected: + + TSharedPtr<IHttpRequest, ESPMode::ThreadSafe> HttpRequest; + + virtual bool Create(); + virtual bool Create( const FString& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default ); + virtual bool Create( const TArray<uint8>& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default ); + virtual bool Create( const FJsonLibraryValue& Content ); + + virtual bool Process(); + +public: + + bool IsRunning() const; + bool IsComplete() const; + + bool Send(); + bool Send( const FString& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default ); + bool Send( const TArray<uint8>& Content, EHttpLibraryContentType ContentType = EHttpLibraryContentType::Default ); + bool Send( const FJsonLibraryValue& Content ); + + void Cancel(); + void Reset(); +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/HttpLibraryBlueprintSupport.Build.cs b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/HttpLibraryBlueprintSupport.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..a4fc1176eb4d2f0a0c6332943f519a48f1503b0d --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/HttpLibraryBlueprintSupport.Build.cs @@ -0,0 +1,20 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +namespace UnrealBuildTool.Rules +{ + public class HttpLibraryBlueprintSupport : ModuleRules + { + public HttpLibraryBlueprintSupport(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "BlueprintGraph", + "HttpLibrary" + } + ); + } + } +} diff --git a/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/HttpLibraryBlueprintSupportModule.cpp b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/HttpLibraryBlueprintSupportModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4613f281bb7cf1cba5db41a703c0bd6ad02c97c4 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/HttpLibraryBlueprintSupportModule.cpp @@ -0,0 +1,4 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE( FDefaultModuleImpl, HttpLibraryBlueprintSupport ); diff --git a/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryGetRequest.cpp b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryGetRequest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0a6f451daf5a9b1b3cabef7fa273d0d356c86ffe --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryGetRequest.cpp @@ -0,0 +1,49 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "K2Node_HttpLibraryGetRequest.h" +#include "HttpLibraryGetRequestCallbackProxy.h" +#include "EdGraph/EdGraphPin.h" + +#define LOCTEXT_NAMESPACE "K2Node" + +UK2Node_HttpLibraryGetRequest::UK2Node_HttpLibraryGetRequest( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED( UHttpLibraryGetRequestCallbackProxy, CreateProxyObjectForGet ); + ProxyFactoryClass = UHttpLibraryGetRequestCallbackProxy::StaticClass(); + + ProxyClass = UHttpLibraryGetRequestCallbackProxy::StaticClass(); +} + +FText UK2Node_HttpLibraryGetRequest::GetTooltipText() const +{ + return LOCTEXT( "K2Node_HttpLibraryGetRequest_Tooltip", "Send an HTTP GET request" ); +} +FText UK2Node_HttpLibraryGetRequest::GetNodeTitle( ENodeTitleType::Type TitleType ) const +{ + return LOCTEXT( "HttpLibraryGetRequest", "HTTP GET Request" ); +} +void UK2Node_HttpLibraryGetRequest::GetPinHoverText( const UEdGraphPin& Pin, FString& HoverTextOut ) const +{ + Super::GetPinHoverText( Pin, HoverTextOut ); + + static FName NAME_OnSuccess = FName( TEXT( "OnSuccess" ) ); + static FName NAME_OnFailure = FName( TEXT( "OnFailure" ) ); + + if ( Pin.PinName == NAME_OnSuccess ) + { + FText ToolTipText = LOCTEXT( "K2Node_HttpLibraryGetRequest_OnSuccess_Tooltip", "Event called when the HTTP request has successfully completed." ); + HoverTextOut = FString::Printf( TEXT( "%s\n%s" ), *ToolTipText.ToString(), *HoverTextOut ); + } + else if ( Pin.PinName == NAME_OnFailure ) + { + FText ToolTipText = LOCTEXT( "K2Node_HttpLibraryGetRequest_OnFailure_Tooltip", "Event called when the HTTP request has failed with an error code." ); + HoverTextOut = FString::Printf( TEXT( "%s\n%s" ), *ToolTipText.ToString(), *HoverTextOut ); + } +} + +FText UK2Node_HttpLibraryGetRequest::GetMenuCategory() const +{ + return LOCTEXT( "HttpLibraryGetRequestCategory", "HTTP Library" ); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryPostRequest.cpp b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryPostRequest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..71e62fa08dc96afc043578faaa04e20e653276ff --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryPostRequest.cpp @@ -0,0 +1,49 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "K2Node_HttpLibraryPostRequest.h" +#include "HttpLibraryPostRequestCallbackProxy.h" +#include "EdGraph/EdGraphPin.h" + +#define LOCTEXT_NAMESPACE "K2Node" + +UK2Node_HttpLibraryPostRequest::UK2Node_HttpLibraryPostRequest( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED( UHttpLibraryPostRequestCallbackProxy, CreateProxyObjectForPost ); + ProxyFactoryClass = UHttpLibraryPostRequestCallbackProxy::StaticClass(); + + ProxyClass = UHttpLibraryPostRequestCallbackProxy::StaticClass(); +} + +FText UK2Node_HttpLibraryPostRequest::GetTooltipText() const +{ + return LOCTEXT( "K2Node_HttpLibraryPostRequest_Tooltip", "Send an HTTP POST request" ); +} +FText UK2Node_HttpLibraryPostRequest::GetNodeTitle( ENodeTitleType::Type TitleType ) const +{ + return LOCTEXT( "HttpLibraryPostRequest", "HTTP POST Request" ); +} +void UK2Node_HttpLibraryPostRequest::GetPinHoverText( const UEdGraphPin& Pin, FString& HoverTextOut ) const +{ + Super::GetPinHoverText( Pin, HoverTextOut ); + + static FName NAME_OnSuccess = FName( TEXT( "OnSuccess" ) ); + static FName NAME_OnFailure = FName( TEXT( "OnFailure" ) ); + + if ( Pin.PinName == NAME_OnSuccess ) + { + FText ToolTipText = LOCTEXT( "K2Node_HttpLibraryPostRequest_OnSuccess_Tooltip", "Event called when the HTTP request has successfully completed." ); + HoverTextOut = FString::Printf( TEXT( "%s\n%s" ), *ToolTipText.ToString(), *HoverTextOut ); + } + else if ( Pin.PinName == NAME_OnFailure ) + { + FText ToolTipText = LOCTEXT( "K2Node_HttpLibraryPostRequest_OnFailure_Tooltip", "Event called when the HTTP request has failed with an error code." ); + HoverTextOut = FString::Printf( TEXT( "%s\n%s" ), *ToolTipText.ToString(), *HoverTextOut ); + } +} + +FText UK2Node_HttpLibraryPostRequest::GetMenuCategory() const +{ + return LOCTEXT( "HttpLibraryPostRequestCategory", "HTTP Library" ); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryRequest.cpp b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryRequest.cpp new file mode 100644 index 0000000000000000000000000000000000000000..300d892443cd7be865b84705630b1be8a8c4e3a9 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Private/K2Node_HttpLibraryRequest.cpp @@ -0,0 +1,55 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "K2Node_HttpLibraryRequest.h" +#include "HttpLibraryRequestCallbackProxy.h" +#include "EdGraph/EdGraphPin.h" + +#define LOCTEXT_NAMESPACE "K2Node" + +UK2Node_HttpLibraryRequest::UK2Node_HttpLibraryRequest( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + ProxyFactoryFunctionName = GET_FUNCTION_NAME_CHECKED( UHttpLibraryRequestCallbackProxy, CreateProxyObjectForRequest ); + ProxyFactoryClass = UHttpLibraryRequestCallbackProxy::StaticClass(); + + ProxyClass = UHttpLibraryRequestCallbackProxy::StaticClass(); +} + +FText UK2Node_HttpLibraryRequest::GetTooltipText() const +{ + return LOCTEXT( "K2Node_HttpLibraryRequest_Tooltip", "Send an HTTP request" ); +} +FText UK2Node_HttpLibraryRequest::GetNodeTitle( ENodeTitleType::Type TitleType ) const +{ + return LOCTEXT( "HttpLibraryRequest", "HTTP Request" ); +} +void UK2Node_HttpLibraryRequest::GetPinHoverText( const UEdGraphPin& Pin, FString& HoverTextOut ) const +{ + Super::GetPinHoverText( Pin, HoverTextOut ); + + static FName NAME_OnSuccess = FName( TEXT( "OnSuccess" ) ); + static FName NAME_OnProgress = FName( TEXT( "OnProgress" ) ); + static FName NAME_OnFailure = FName( TEXT( "OnFailure" ) ); + + if ( Pin.PinName == NAME_OnSuccess ) + { + FText ToolTipText = LOCTEXT( "K2Node_HttpLibraryRequest_OnSuccess_Tooltip", "Event called when the HTTP request has successfully completed." ); + HoverTextOut = FString::Printf( TEXT( "%s\n%s" ), *ToolTipText.ToString(), *HoverTextOut ); + } + else if ( Pin.PinName == NAME_OnProgress ) + { + FText ToolTipText = LOCTEXT( "K2Node_HttpLibraryRequest_OnProgress_Tooltip", "Event called when the HTTP request has a progress update." ); + HoverTextOut = FString::Printf( TEXT( "%s\n%s" ), *ToolTipText.ToString(), *HoverTextOut ); + } + else if ( Pin.PinName == NAME_OnFailure ) + { + FText ToolTipText = LOCTEXT( "K2Node_HttpLibraryRequest_OnFailure_Tooltip", "Event called when the HTTP request has failed with an error code." ); + HoverTextOut = FString::Printf( TEXT( "%s\n%s" ), *ToolTipText.ToString(), *HoverTextOut ); + } +} + +FText UK2Node_HttpLibraryRequest::GetMenuCategory() const +{ + return LOCTEXT( "HttpLibraryRequestCategory", "HTTP Library" ); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryGetRequest.h b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryGetRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..f7b53b699edbc41d4889732992d3f9183dcdd70d --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryGetRequest.h @@ -0,0 +1,18 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "K2Node_BaseAsyncTask.h" +#include "K2Node_HttpLibraryGetRequest.generated.h" + +UCLASS() +class HTTPLIBRARYBLUEPRINTSUPPORT_API UK2Node_HttpLibraryGetRequest : public UK2Node_BaseAsyncTask +{ + GENERATED_UCLASS_BODY() + + virtual FText GetTooltipText() const override; + virtual FText GetNodeTitle( ENodeTitleType::Type TitleType ) const override; + virtual void GetPinHoverText( const UEdGraphPin& Pin, FString& HoverTextOut ) const override; + + virtual FText GetMenuCategory() const override; +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryPostRequest.h b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryPostRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..936fbe88b44b285ade197d7318ef53f393d0d3fe --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryPostRequest.h @@ -0,0 +1,18 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "K2Node_BaseAsyncTask.h" +#include "K2Node_HttpLibraryPostRequest.generated.h" + +UCLASS() +class HTTPLIBRARYBLUEPRINTSUPPORT_API UK2Node_HttpLibraryPostRequest : public UK2Node_BaseAsyncTask +{ + GENERATED_UCLASS_BODY() + + virtual FText GetTooltipText() const override; + virtual FText GetNodeTitle( ENodeTitleType::Type TitleType ) const override; + virtual void GetPinHoverText( const UEdGraphPin& Pin, FString& HoverTextOut ) const override; + + virtual FText GetMenuCategory() const override; +}; diff --git a/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryRequest.h b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryRequest.h new file mode 100644 index 0000000000000000000000000000000000000000..3e7f2a5f34d49793032522e8162228fbf980f5d7 --- /dev/null +++ b/Plugins/HttpLibrary/Source/HttpLibraryBlueprintSupport/Public/K2Node_HttpLibraryRequest.h @@ -0,0 +1,18 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "K2Node_BaseAsyncTask.h" +#include "K2Node_HttpLibraryRequest.generated.h" + +UCLASS() +class HTTPLIBRARYBLUEPRINTSUPPORT_API UK2Node_HttpLibraryRequest : public UK2Node_BaseAsyncTask +{ + GENERATED_UCLASS_BODY() + + virtual FText GetTooltipText() const override; + virtual FText GetNodeTitle( ENodeTitleType::Type TitleType ) const override; + virtual void GetPinHoverText( const UEdGraphPin& Pin, FString& HoverTextOut ) const override; + + virtual FText GetMenuCategory() const override; +}; diff --git a/Plugins/JsonLibrary/JsonLibrary.uplugin b/Plugins/JsonLibrary/JsonLibrary.uplugin new file mode 100644 index 0000000000000000000000000000000000000000..162e227eb92537ee6155895d8e79626baea6690a --- /dev/null +++ b/Plugins/JsonLibrary/JsonLibrary.uplugin @@ -0,0 +1,40 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "JSON Library", + "Description": "Manage objects, arrays, and primitive data types using JSON.", + "Category": "Messaging", + "CreatedBy": "Tracer Interactive", + "CreatedByURL": "https://tracerinteractive.com", + "DocsURL": "https://cdn.tracerinteractive.com/jsonlibrary/documentation.pdf", + "MarketplaceURL": "", + "SupportURL": "", + "EngineVersion": "5.1.0", + "CanContainContent": false, + "Installed": true, + "Modules": [ + { + "Name": "JsonLibrary", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ + "Win64", + "Mac", + "Linux", + "Android", + "IOS" + ] + }, + { + "Name": "JsonLibraryBlueprintSupport", + "Type": "UncookedOnly", + "LoadingPhase": "PostConfigInit", + "WhitelistPlatforms": [ + "Win64", + "Mac", + "Linux" + ] + } + ] +} \ No newline at end of file diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/JsonLibrary.Build.cs b/Plugins/JsonLibrary/Source/JsonLibrary/JsonLibrary.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..e1932329f7d578152edf704e5c701fc94377ffa4 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/JsonLibrary.Build.cs @@ -0,0 +1,20 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +namespace UnrealBuildTool.Rules +{ + public class JsonLibrary : ModuleRules + { + public JsonLibrary(ReadOnlyTargetRules Target) : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "Json", + "JsonUtilities" + } + ); + } + } +} diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryBlueprintHelpers.cpp b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryBlueprintHelpers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7e15320fecd8305935a83450f75df94fb7de10d0 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryBlueprintHelpers.cpp @@ -0,0 +1,58 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "JsonLibraryBlueprintHelpers.h" +#include "Engine/UserDefinedStruct.h" + +bool UJsonLibraryBlueprintHelpers::StructFromJson( const UScriptStruct* StructType, const FJsonLibraryObject& Object, FStructBase& OutStruct ) +{ + check( 0 ); + return false; +} + +FJsonLibraryObject UJsonLibraryBlueprintHelpers::StructToJson( const UScriptStruct* StructType, const FStructBase& Struct ) +{ + check( 0 ); + return FJsonLibraryObject(); +} + +bool UJsonLibraryBlueprintHelpers::Generic_StructFromJson( const UScriptStruct* StructType, const FJsonLibraryObject& Object, void* OutStructPtr ) +{ + if ( !StructType || !OutStructPtr ) + return false; + if ( !Object.IsValid() ) + return false; + + return Object.ToStruct( StructType, OutStructPtr ); +} + +bool UJsonLibraryBlueprintHelpers::Generic_StructToJson( const UScriptStruct* StructType, void* StructPtr, FJsonLibraryObject& OutObject ) +{ + if ( !StructType || !StructPtr ) + return false; + + OutObject = FJsonLibraryObject( StructType, StructPtr ); + return OutObject.IsValid(); +} + +FJsonLibraryObject UJsonLibraryBlueprintHelpers::ConstructInvalidObject() +{ + return FJsonLibraryObject( TSharedPtr<FJsonValueObject>() ); +} + +bool UJsonLibraryBlueprintHelpers::IsValidObject( const FJsonLibraryObject& Object ) +{ + return Object.IsValid(); +} + +bool UJsonLibraryBlueprintHelpers::InitializeStructData( const FJsonLibraryObject& Object, const UScriptStruct* StructType, FStructOnScope& StructData ) +{ + if ( !StructType ) + return false; + + StructData.Initialize( StructType ); + + void* StructPtr = StructData.GetStructMemory(); + if ( !StructPtr ) + return false; + + return Object.ToStruct( StructType, StructPtr ); +} diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryConverter.cpp b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryConverter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..61d84f1893dfbb1a487e9e7647a27d6490c9ae30 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryConverter.cpp @@ -0,0 +1,1148 @@ +// Engine/Source/Runtime/JsonUtilities/Private/JsonObjectConverter.cpp + +#include "JsonLibraryConverter.h" +#include "Internationalization/Culture.h" +#include "UObject/ObjectMacros.h" +#include "UObject/Class.h" +#include "UObject/UnrealType.h" +#include "UObject/EnumProperty.h" +#include "UObject/TextProperty.h" +#include "UObject/PropertyPortFlags.h" +#include "UObject/Package.h" +#include "Policies/CondensedJsonPrintPolicy.h" +#include "JsonObjectWrapper.h" +#include "Engine/UserDefinedStruct.h" + +#define LOCTEXT_NAMESPACE "JsonLibraryConverter" + +FString FJsonLibraryConverter::StandardizeCase(const FString &StringIn) +{ + // this probably won't work for all cases, consider downcasing the string fully + FString FixedString = StringIn; + FixedString[0] = FChar::ToLower(FixedString[0]); // our json classes/variable start lower case + FixedString.ReplaceInline(TEXT("ID"), TEXT("Id"), ESearchCase::CaseSensitive); // Id is standard instead of ID, some of our fnames use ID + return FixedString; +} + + +namespace +{ + const FString ObjectClassNameKey = "_ClassName"; + +/** Convert property to JSON, assuming either the property is not an array or the value is an individual array element */ +TSharedPtr<FJsonValue> ConvertScalarFPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const FJsonLibraryConverter::CustomExportCallback* ExportCb, FProperty* OuterProperty) +{ + // See if there's a custom export callback first, so it can override default behavior + if (ExportCb && ExportCb->IsBound()) + { + TSharedPtr<FJsonValue> CustomValue = ExportCb->Execute(Property, Value); + if (CustomValue.IsValid()) + { + return CustomValue; + } + // fall through to default cases + } + + if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property)) + { + // export enums as strings + UEnum* EnumDef = EnumProperty->GetEnum(); + FString StringValue = EnumDef->GetAuthoredNameStringByValue(EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(Value)); + return MakeShared<FJsonValueString>(StringValue); + } + else if (FNumericProperty *NumericProperty = CastField<FNumericProperty>(Property)) + { + // see if it's an enum + UEnum* EnumDef = NumericProperty->GetIntPropertyEnum(); + if (EnumDef != NULL) + { + // export enums as strings + FString StringValue = EnumDef->GetAuthoredNameStringByValue(NumericProperty->GetSignedIntPropertyValue(Value)); + return MakeShared<FJsonValueString>(StringValue); + } + + // We want to export numbers as numbers + if (NumericProperty->IsFloatingPoint()) + { + return MakeShared<FJsonValueNumber>(NumericProperty->GetFloatingPointPropertyValue(Value)); + } + else if (NumericProperty->IsInteger()) + { + return MakeShared<FJsonValueNumber>(NumericProperty->GetSignedIntPropertyValue(Value)); + } + + // fall through to default + } + else if (FBoolProperty *BoolProperty = CastField<FBoolProperty>(Property)) + { + // Export bools as bools + return MakeShared<FJsonValueBoolean>(BoolProperty->GetPropertyValue(Value)); + } + else if (FStrProperty *StringProperty = CastField<FStrProperty>(Property)) + { + return MakeShared<FJsonValueString>(StringProperty->GetPropertyValue(Value)); + } + else if (FTextProperty *TextProperty = CastField<FTextProperty>(Property)) + { + return MakeShared<FJsonValueString>(TextProperty->GetPropertyValue(Value).ToString()); + } + else if (FArrayProperty *ArrayProperty = CastField<FArrayProperty>(Property)) + { + TArray< TSharedPtr<FJsonValue> > Out; + FScriptArrayHelper Helper(ArrayProperty, Value); + for (int32 i=0, n=Helper.Num(); i<n; ++i) + { + TSharedPtr<FJsonValue> Elem = FJsonLibraryConverter::UPropertyToJsonValue(ArrayProperty->Inner, Helper.GetRawPtr(i), CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb, ArrayProperty); + if ( Elem.IsValid() ) + { + // add to the array + Out.Push(Elem); + } + } + return MakeShared<FJsonValueArray>(Out); + } + else if ( FSetProperty* SetProperty = CastField<FSetProperty>(Property) ) + { + TArray< TSharedPtr<FJsonValue> > Out; + FScriptSetHelper Helper(SetProperty, Value); + for ( int32 i=0, n=Helper.Num(); n; ++i ) + { + if ( Helper.IsValidIndex(i) ) + { + TSharedPtr<FJsonValue> Elem = FJsonLibraryConverter::UPropertyToJsonValue(SetProperty->ElementProp, Helper.GetElementPtr(i), CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb, SetProperty); + if ( Elem.IsValid() ) + { + // add to the array + Out.Push(Elem); + } + + --n; + } + } + return MakeShared<FJsonValueArray>(Out); + } + else if ( FMapProperty* MapProperty = CastField<FMapProperty>(Property) ) + { + TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>(); + + FScriptMapHelper Helper(MapProperty, Value); + for ( int32 i=0, n = Helper.Num(); n; ++i ) + { + if ( Helper.IsValidIndex(i) ) + { + TSharedPtr<FJsonValue> KeyElement = FJsonLibraryConverter::UPropertyToJsonValue(MapProperty->KeyProp, Helper.GetKeyPtr(i), CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb, MapProperty); + TSharedPtr<FJsonValue> ValueElement = FJsonLibraryConverter::UPropertyToJsonValue(MapProperty->ValueProp, Helper.GetValuePtr(i), CheckFlags & ( ~CPF_ParmFlags ), SkipFlags, ExportCb, MapProperty); + if ( KeyElement.IsValid() && ValueElement.IsValid() ) + { + FString KeyString; + if (!KeyElement->TryGetString(KeyString)) + { + MapProperty->KeyProp->ExportTextItem_Direct(KeyString, Helper.GetKeyPtr(i), nullptr, nullptr, 0); + if (KeyString.IsEmpty()) + { + UE_LOG(LogJson, Error, TEXT("Unable to convert key to string for property %s."), *MapProperty->GetAuthoredName()) + KeyString = FString::Printf(TEXT("Unparsed Key %d"), i); + } + } + + // Coerce camelCase map keys for Enum/FName properties + if (CastField<FEnumProperty>(MapProperty->KeyProp) || + CastField<FNameProperty>(MapProperty->KeyProp)) + { + KeyString = FJsonLibraryConverter::StandardizeCase(KeyString); + } + Out->SetField(KeyString, ValueElement); + } + + --n; + } + } + + return MakeShared<FJsonValueObject>(Out); + } + else if (FStructProperty *StructProperty = CastField<FStructProperty>(Property)) + { + UScriptStruct::ICppStructOps* TheCppStructOps = StructProperty->Struct->GetCppStructOps(); + // Intentionally exclude the JSON Object wrapper, which specifically needs to export JSON in an object representation instead of a string + if (StructProperty->Struct != FJsonObjectWrapper::StaticStruct() && TheCppStructOps && TheCppStructOps->HasExportTextItem()) + { + FString OutValueStr; + TheCppStructOps->ExportTextItem(OutValueStr, Value, nullptr, nullptr, PPF_None, nullptr); + return MakeShared<FJsonValueString>(OutValueStr); + } + + TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>(); + if (FJsonLibraryConverter::UStructToJsonObject(StructProperty->Struct, Value, Out, CheckFlags & (~CPF_ParmFlags), SkipFlags, ExportCb)) + { + return MakeShared<FJsonValueObject>(Out); + } + } + else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property)) + { + // Instanced properties should be copied by value, while normal UObject* properties should output as asset references + UObject* Object = ObjectProperty->GetObjectPropertyValue(Value); + if (Object && (ObjectProperty->HasAnyPropertyFlags(CPF_PersistentInstance) || (OuterProperty && OuterProperty->HasAnyPropertyFlags(CPF_PersistentInstance)))) + { + TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>(); + + Out->SetStringField(ObjectClassNameKey, Object->GetClass()->GetPathName()); + if (FJsonLibraryConverter::UStructToJsonObject(ObjectProperty->GetObjectPropertyValue(Value)->GetClass(), Object, Out, CheckFlags, SkipFlags, ExportCb)) + { + TSharedRef<FJsonValueObject> JsonObject = MakeShared<FJsonValueObject>(Out); + JsonObject->Type = EJson::Object; + return JsonObject; + } + } + else + { + FString StringValue; + Property->ExportTextItem_Direct(StringValue, Value, nullptr, nullptr, PPF_None); + return MakeShared<FJsonValueString>(StringValue); + } + } + else + { + // Default to export as string for everything else + FString StringValue; + Property->ExportTextItem_Direct(StringValue, Value, NULL, NULL, PPF_None); + return MakeShared<FJsonValueString>(StringValue); + } + + // invalid + return TSharedPtr<FJsonValue>(); +} +} + +PRAGMA_DISABLE_DEPRECATION_WARNINGS + +TSharedPtr<FJsonValue> FJsonLibraryConverter::ObjectJsonCallback(FProperty* Property, const void* Value) +{ + if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(Property)) + { + if (!ObjectProperty->HasAnyFlags(RF_Transient)) // We are taking Transient to mean we don't want to serialize to Json either (could make a new flag if necessary) + { + TSharedRef<FJsonObject> Out = MakeShared<FJsonObject>(); + + CustomExportCallback CustomCB; + CustomCB.BindStatic(FJsonLibraryConverter::ObjectJsonCallback); + + void** PtrToValuePtr = (void**)Value; + + if (FJsonLibraryConverter::UStructToJsonObject(ObjectProperty->PropertyClass, (*PtrToValuePtr), Out, 0, 0, &CustomCB)) //-V549 + { + return MakeShared<FJsonValueObject>(Out); + } + } + } + + // invalid + return TSharedPtr<FJsonValue>(); +} + +PRAGMA_ENABLE_DEPRECATION_WARNINGS + +TSharedPtr<FJsonValue> FJsonLibraryConverter::UPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb, FProperty* OuterProperty) +{ + if (Property->ArrayDim == 1) + { + return ConvertScalarFPropertyToJsonValue(Property, Value, CheckFlags, SkipFlags, ExportCb, OuterProperty); + } + + TArray< TSharedPtr<FJsonValue> > Array; + for (int Index = 0; Index != Property->ArrayDim; ++Index) + { + Array.Add(ConvertScalarFPropertyToJsonValue(Property, (char*)Value + Index * Property->ElementSize, CheckFlags, SkipFlags, ExportCb, OuterProperty)); + } + return MakeShared<FJsonValueArray>(Array); +} + +bool FJsonLibraryConverter::UStructToJsonObject(const UStruct* StructDefinition, const void* Struct, TSharedRef<FJsonObject> OutJsonObject, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb) +{ + return UStructToJsonAttributes(StructDefinition, Struct, OutJsonObject->Values, CheckFlags, SkipFlags, ExportCb); +} + +bool FJsonLibraryConverter::UStructToJsonAttributes(const UStruct* StructDefinition, const void* Struct, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags, int64 SkipFlags, const CustomExportCallback* ExportCb) +{ + if (SkipFlags == 0) + { + // If we have no specified skip flags, skip deprecated, transient and skip serialization by default when writing + SkipFlags |= CPF_Deprecated | CPF_Transient; + } + +// ---------- UUserDefinedStruct ---------- + bool bUserStruct = StructDefinition->IsA( UUserDefinedStruct::StaticClass() ); +// ---------- UUserDefinedStruct ---------- + + if (StructDefinition == FJsonObjectWrapper::StaticStruct()) + { + // Just copy it into the object + const FJsonObjectWrapper* ProxyObject = (const FJsonObjectWrapper *)Struct; + + if (ProxyObject->JsonObject.IsValid()) + { + OutJsonAttributes = ProxyObject->JsonObject->Values; + } + return true; + } + + for (TFieldIterator<FProperty> It(StructDefinition); It; ++It) + { + FProperty* Property = *It; + + // Check to see if we should ignore this property + if (CheckFlags != 0 && !Property->HasAnyPropertyFlags(CheckFlags)) + { + continue; + } + if (Property->HasAnyPropertyFlags(SkipFlags)) + { + continue; + } + +// ---------- UUserDefinedStruct::GetAuthoredNameForField() ---------- + FString PropertyName = Property->GetAuthoredName(); + if (bUserStruct) + { + const int32 GuidStrLen = 32; + const int32 MinimalPostfixlen = GuidStrLen + 3; + if (PropertyName.Len() > MinimalPostfixlen) + { + FString DisplayName = PropertyName.LeftChop(GuidStrLen + 1); + int FirstCharToRemove = -1; + const bool bCharFound = DisplayName.FindLastChar(TCHAR('_'), FirstCharToRemove); + if (bCharFound && (FirstCharToRemove > 0)) + { + PropertyName = DisplayName.Mid(0, FirstCharToRemove); + } + } + } +// ---------- UUserDefinedStruct::GetAuthoredNameForField() ---------- + + FString VariableName = StandardizeCase(PropertyName); + const void* Value = Property->ContainerPtrToValuePtr<uint8>(Struct); + + // convert the property to a FJsonValue + TSharedPtr<FJsonValue> JsonValue = UPropertyToJsonValue(Property, Value, CheckFlags, SkipFlags, ExportCb); + if (!JsonValue.IsValid()) + { + FFieldClass* PropClass = Property->GetClass(); + UE_LOG(LogJson, Error, TEXT("UStructToJsonObject - Unhandled property type '%s': %s"), *PropClass->GetName(), *Property->GetPathName()); + return false; + } + + // set the value on the output object + OutJsonAttributes.Add(VariableName, JsonValue); + } + + return true; +} + +template<class CharType, class PrintPolicy> +bool UStructToJsonObjectStringInternal(const TSharedRef<FJsonObject>& JsonObject, FString& OutJsonString, int32 Indent) +{ + TSharedRef<TJsonWriter<CharType, PrintPolicy> > JsonWriter = TJsonWriterFactory<CharType, PrintPolicy>::Create(&OutJsonString, Indent); + bool bSuccess = FJsonSerializer::Serialize(JsonObject, JsonWriter); + JsonWriter->Close(); + return bSuccess; +} + +bool FJsonLibraryConverter::UStructToJsonObjectString(const UStruct* StructDefinition, const void* Struct, FString& OutJsonString, int64 CheckFlags, int64 SkipFlags, int32 Indent, const CustomExportCallback* ExportCb, bool bPrettyPrint) +{ + TSharedRef<FJsonObject> JsonObject = MakeShared<FJsonObject>(); + if (UStructToJsonObject(StructDefinition, Struct, JsonObject, CheckFlags, SkipFlags, ExportCb)) + { + bool bSuccess = false; + if (bPrettyPrint) + { + bSuccess = UStructToJsonObjectStringInternal<TCHAR, TPrettyJsonPrintPolicy<TCHAR> >(JsonObject, OutJsonString, Indent); + } + else + { + bSuccess = UStructToJsonObjectStringInternal<TCHAR, TCondensedJsonPrintPolicy<TCHAR> >(JsonObject, OutJsonString, Indent); + } + if (bSuccess) + { + return true; + } + else + { + UE_LOG(LogJson, Warning, TEXT("UStructToJsonObjectString - Unable to write out JSON")); + } + } + + return false; +} + +//static +bool FJsonLibraryConverter::GetTextFromObject(const TSharedRef<FJsonObject>& Obj, FText& TextOut) +{ + // get the prioritized culture name list + FCultureRef CurrentCulture = FInternationalization::Get().GetCurrentCulture(); + TArray<FString> CultureList = CurrentCulture->GetPrioritizedParentCultureNames(); + + // try to follow the fall back chain that the engine uses + FString TextString; + for (const FString& CultureCode : CultureList) + { + if (Obj->TryGetStringField(CultureCode, TextString)) + { + TextOut = FText::FromString(TextString); + return true; + } + } + + // try again but only search on the locale region (in the localized data). This is a common omission (i.e. en-US source text should be used if no en is defined) + for (const FString& LocaleToMatch : CultureList) + { + int32 SeparatorPos; + // only consider base language entries in culture chain (i.e. "en") + if (!LocaleToMatch.FindChar('-', SeparatorPos)) + { + for (const auto& Pair : Obj->Values) + { + // only consider coupled entries now (base ones would have been matched on first path) (i.e. "en-US") + if (Pair.Key.FindChar('-', SeparatorPos)) + { + if (Pair.Key.StartsWith(LocaleToMatch)) + { + TextOut = FText::FromString(Pair.Value->AsString()); + return true; + } + } + } + } + } + + // no luck, is this possibly an unrelated json object? + return false; +} + + +namespace +{ + bool JsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason); + bool JsonAttributesToUStructWithContainer(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason); + + /** Convert JSON to property, assuming either the property is not an array or the value is an individual array element */ + bool ConvertScalarJsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason) + { + if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property)) + { + if (JsonValue->Type == EJson::String) + { + // see if we were passed a string for the enum + const UEnum* Enum = EnumProperty->GetEnum(); + check(Enum); + FString StrValue = JsonValue->AsString(); + int64 IntValue = Enum->GetValueByName(FName(*StrValue), EGetByNameFlags::CheckAuthoredName); + if (IntValue == INDEX_NONE) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import enum %s from string value %s for property %s"), *Enum->CppType, *StrValue, *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportEnumFromString", "Unable to import enum {0} from string value {1} for property {2}"), FText::FromString(Enum->CppType), FText::FromString(StrValue), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(OutValue, IntValue); + } + else + { + // AsNumber will log an error for completely inappropriate types (then give us a default) + EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(OutValue, (int64)JsonValue->AsNumber()); + } + } + else if (FNumericProperty *NumericProperty = CastField<FNumericProperty>(Property)) + { + if (NumericProperty->IsEnum() && JsonValue->Type == EJson::String) + { + // see if we were passed a string for the enum + const UEnum* Enum = NumericProperty->GetIntPropertyEnum(); + check(Enum); // should be assured by IsEnum() + FString StrValue = JsonValue->AsString(); + int64 IntValue = Enum->GetValueByName(FName(*StrValue), EGetByNameFlags::CheckAuthoredName); + if (IntValue == INDEX_NONE) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import enum %s from numeric value %s for property %s"), *Enum->CppType, *StrValue, *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportEnumFromNumeric", "Unable to import enum {0} from numeric value {1} for property {2}"), FText::FromString(Enum->CppType), FText::FromString(StrValue), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + NumericProperty->SetIntPropertyValue(OutValue, IntValue); + } + else if (NumericProperty->IsFloatingPoint()) + { + // AsNumber will log an error for completely inappropriate types (then give us a default) + NumericProperty->SetFloatingPointPropertyValue(OutValue, JsonValue->AsNumber()); + } + else if (NumericProperty->IsInteger()) + { + if (JsonValue->Type == EJson::String) + { + // parse string -> int64 ourselves so we don't lose any precision going through AsNumber (aka double) + NumericProperty->SetIntPropertyValue(OutValue, FCString::Atoi64(*JsonValue->AsString())); + } + else + { + // AsNumber will log an error for completely inappropriate types (then give us a default) + NumericProperty->SetIntPropertyValue(OutValue, (int64)JsonValue->AsNumber()); + } + } + else + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import json value into %s numeric property %s"), *Property->GetClass()->GetName(), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportNumericProperty", "Unable to import json value into {0} numeric property {1}"), FText::FromString(Property->GetClass()->GetName()), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + else if (FBoolProperty *BoolProperty = CastField<FBoolProperty>(Property)) + { + // AsBool will log an error for completely inappropriate types (then give us a default) + BoolProperty->SetPropertyValue(OutValue, JsonValue->AsBool()); + } + else if (FStrProperty *StringProperty = CastField<FStrProperty>(Property)) + { + // AsString will log an error for completely inappropriate types (then give us a default) + StringProperty->SetPropertyValue(OutValue, JsonValue->AsString()); + } + else if (FArrayProperty *ArrayProperty = CastField<FArrayProperty>(Property)) + { + if (JsonValue->Type == EJson::Array) + { + TArray< TSharedPtr<FJsonValue> > ArrayValue = JsonValue->AsArray(); + int32 ArrLen = ArrayValue.Num(); + + // make the output array size match + FScriptArrayHelper Helper(ArrayProperty, OutValue); + Helper.Resize(ArrLen); + + // set the property values + for (int32 i = 0; i < ArrLen; ++i) + { + const TSharedPtr<FJsonValue>& ArrayValueItem = ArrayValue[i]; + if (ArrayValueItem.IsValid() && !ArrayValueItem->IsNull()) + { + if (!JsonValueToFPropertyWithContainer(ArrayValueItem, ArrayProperty->Inner, Helper.GetRawPtr(i), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason)) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import Array element %d for property %s"), i, *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportArrayElement", "Unable to import Array element {0} for property {1}\n{2}"), FText::AsNumber(i), FText::FromString(Property->GetAuthoredName()), *OutFailReason); + } + return false; + } + } + } + } + else + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import non-array JSON value into Array property %s"), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportArray", "Unable to import non-array JSON value into Array property {0}"), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + else if (FMapProperty* MapProperty = CastField<FMapProperty>(Property)) + { + if (JsonValue->Type == EJson::Object) + { + TSharedPtr<FJsonObject> ObjectValue = JsonValue->AsObject(); + + FScriptMapHelper Helper(MapProperty, OutValue); + + check(ObjectValue); + + int32 MapSize = ObjectValue->Values.Num(); + Helper.EmptyValues(MapSize); + + // set the property values + for (const auto& Entry : ObjectValue->Values) + { + if (Entry.Value.IsValid() && !Entry.Value->IsNull()) + { + int32 NewIndex = Helper.AddDefaultValue_Invalid_NeedsRehash(); + + TSharedPtr<FJsonValueString> TempKeyValue = MakeShared<FJsonValueString>(Entry.Key); + + if (!JsonValueToFPropertyWithContainer(TempKeyValue, MapProperty->KeyProp, Helper.GetKeyPtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason)) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import Map element %s key for property %s"), *Entry.Key, *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportMapElementKey", "Unable to import Map element {0} key for property {1}\n{2}"), FText::FromString(Entry.Key), FText::FromString(Property->GetAuthoredName()), *OutFailReason); + } + return false; + } + + if (!JsonValueToFPropertyWithContainer(Entry.Value, MapProperty->ValueProp, Helper.GetValuePtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason)) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import Map element %s value for property %s"), *Entry.Key, *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportMapElementValue", "Unable to import Map element {0} value for property {1}\n{2}"), FText::FromString(Entry.Key), FText::FromString(Property->GetAuthoredName()), *OutFailReason); + } + return false; + } + } + } + + Helper.Rehash(); + } + else + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import non-object JSON value into Map property %s"), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportMap", "Unable to import non-object JSON value into Map property {0}"), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + else if (FSetProperty* SetProperty = CastField<FSetProperty>(Property)) + { + if (JsonValue->Type == EJson::Array) + { + TArray< TSharedPtr<FJsonValue> > ArrayValue = JsonValue->AsArray(); + int32 ArrLen = ArrayValue.Num(); + + FScriptSetHelper Helper(SetProperty, OutValue); + Helper.EmptyElements(ArrLen); + + // set the property values + for (int32 i = 0; i < ArrLen; ++i) + { + const TSharedPtr<FJsonValue>& ArrayValueItem = ArrayValue[i]; + if (ArrayValueItem.IsValid() && !ArrayValueItem->IsNull()) + { + int32 NewIndex = Helper.AddDefaultValue_Invalid_NeedsRehash(); + if (!JsonValueToFPropertyWithContainer(ArrayValueItem, SetProperty->ElementProp, Helper.GetElementPtr(NewIndex), ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason)) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import Set element %d for property %s"), i, *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportSetElement", "Unable to import Set element {0} for property {1}\n{2}"), FText::AsNumber(i), FText::FromString(Property->GetAuthoredName()), *OutFailReason); + } + return false; + } + } + } + + Helper.Rehash(); + } + else + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import non-array JSON value into Set property %s"), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportSet", "Unable to import non-array JSON value into Set property {0}"), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + else if (FTextProperty* TextProperty = CastField<FTextProperty>(Property)) + { + if (JsonValue->Type == EJson::String) + { + // assume this string is already localized, so import as invariant + TextProperty->SetPropertyValue(OutValue, FText::FromString(JsonValue->AsString())); + } + else if (JsonValue->Type == EJson::Object) + { + TSharedPtr<FJsonObject> Obj = JsonValue->AsObject(); + check(Obj.IsValid()); // should not fail if Type == EJson::Object + + // import the subvalue as a culture invariant string + FText Text; + if (!FJsonLibraryConverter::GetTextFromObject(Obj.ToSharedRef(), Text)) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON object with invalid keys into Text property %s"), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportTextFromObject", "Unable to import JSON object with invalid keys into Text property {0}"), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + TextProperty->SetPropertyValue(OutValue, Text); + } + else + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON value that is neither string nor object into Text property %s"), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportText", "Unable to import JSON value that is neither string nor object into Text property {0}"), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + else if (FStructProperty *StructProperty = CastField<FStructProperty>(Property)) + { + static const FName NAME_DateTime(TEXT("DateTime")); + if (JsonValue->Type == EJson::Object) + { + TSharedPtr<FJsonObject> Obj = JsonValue->AsObject(); + check(Obj.IsValid()); // should not fail if Type == EJson::Object + if (!JsonAttributesToUStructWithContainer(Obj->Values, StructProperty->Struct, OutValue, ContainerStruct, Container, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason)) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON object into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportStructFromObject", "Unable to import JSON object into {0} property {1}\n{2}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()), *OutFailReason); + } + return false; + } + } + else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_LinearColor) + { + FLinearColor& ColorOut = *(FLinearColor*)OutValue; + FString ColorString = JsonValue->AsString(); + + FColor IntermediateColor; + IntermediateColor = FColor::FromHex(ColorString); + + ColorOut = IntermediateColor; + } + else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_Color) + { + FColor& ColorOut = *(FColor*)OutValue; + FString ColorString = JsonValue->AsString(); + + ColorOut = FColor::FromHex(ColorString); + } + else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetFName() == NAME_DateTime) + { + FString DateString = JsonValue->AsString(); + FDateTime& DateTimeOut = *(FDateTime*)OutValue; + if (DateString == TEXT("min")) + { + // min representable value for our date struct. Actual date may vary by platform (this is used for sorting) + DateTimeOut = FDateTime::MinValue(); + } + else if (DateString == TEXT("max")) + { + // max representable value for our date struct. Actual date may vary by platform (this is used for sorting) + DateTimeOut = FDateTime::MaxValue(); + } + else if (DateString == TEXT("now")) + { + // this value's not really meaningful from json serialization (since we don't know timezone) but handle it anyway since we're handling the other keywords + DateTimeOut = FDateTime::UtcNow(); + } + else if (FDateTime::ParseIso8601(*DateString, DateTimeOut)) + { + // ok + } + else if (FDateTime::Parse(DateString, DateTimeOut)) + { + // ok + } + else + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into DateTime property %s"), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportDateTimeFromString", "Unable to import JSON string into DateTime property {0}"), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + else if (JsonValue->Type == EJson::String && StructProperty->Struct->GetCppStructOps() && StructProperty->Struct->GetCppStructOps()->HasImportTextItem()) + { + UScriptStruct::ICppStructOps* TheCppStructOps = StructProperty->Struct->GetCppStructOps(); + + FString ImportTextString = JsonValue->AsString(); + const TCHAR* ImportTextPtr = *ImportTextString; + if (!TheCppStructOps->ImportTextItem(ImportTextPtr, OutValue, PPF_None, nullptr, (FOutputDevice*)GWarn)) + { + // Fall back to trying the tagged property approach if custom ImportTextItem couldn't get it done + if (Property->ImportText_Direct(ImportTextPtr, OutValue, nullptr, PPF_None) == nullptr) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportStructFromString", "Unable to import JSON string into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + } + else if (JsonValue->Type == EJson::String) + { + FString ImportTextString = JsonValue->AsString(); + const TCHAR* ImportTextPtr = *ImportTextString; + if (Property->ImportText_Direct(ImportTextPtr, OutValue, nullptr, PPF_None) == nullptr) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportStructFromString", "Unable to import JSON string into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + else + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON value that is neither string nor object into %s property %s"), *StructProperty->Struct->GetAuthoredName(), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportStruct", "Unable to import JSON value that is neither string nor object into {0} property {1}"), FText::FromString(StructProperty->Struct->GetAuthoredName()), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + else if (FObjectProperty *ObjectProperty = CastField<FObjectProperty>(Property)) + { + if (JsonValue->Type == EJson::Object) + { + UObject* Outer = GetTransientPackage(); + if (ContainerStruct->IsChildOf(UObject::StaticClass())) + { + Outer = (UObject*)Container; + } + + TSharedPtr<FJsonObject> Obj = JsonValue->AsObject(); + UClass* PropertyClass = ObjectProperty->PropertyClass; + + // If a specific subclass was stored in the Json, use that instead of the PropertyClass + FString ClassString = Obj->GetStringField(ObjectClassNameKey); + Obj->RemoveField(ObjectClassNameKey); + if (!ClassString.IsEmpty()) + { + UClass* FoundClass = FPackageName::IsShortPackageName(ClassString) ? FindFirstObject<UClass>(*ClassString) : UClass::TryFindTypeSlow<UClass>(ClassString); + if (FoundClass) + { + PropertyClass = FoundClass; + } + } + + UObject* createdObj = StaticAllocateObject(PropertyClass, Outer, NAME_None, EObjectFlags::RF_NoFlags, EInternalObjectFlags::None, false); + (*PropertyClass->ClassConstructor)(FObjectInitializer(createdObj, PropertyClass->ClassDefaultObject, EObjectInitializerOptions::None)); + + ObjectProperty->SetObjectPropertyValue(OutValue, createdObj); + + check(Obj.IsValid()); // should not fail if Type == EJson::Object + if (!JsonAttributesToUStructWithContainer(Obj->Values, PropertyClass, createdObj, PropertyClass, createdObj, CheckFlags & (~CPF_ParmFlags), SkipFlags, bStrictMode, OutFailReason)) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON object into %s property %s"), *PropertyClass->GetAuthoredName(), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportObjectFromObject", "Unable to import JSON object into {0} property {1}\n{2}"), FText::FromString(PropertyClass->GetAuthoredName()), FText::FromString(Property->GetAuthoredName()), *OutFailReason); + } + return false; + } + } + else if (JsonValue->Type == EJson::String) + { + // Default to expect a string for everything else + if (Property->ImportText_Direct(*JsonValue->AsString(), OutValue, nullptr, 0) == nullptr) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into %s property %s"), *ObjectProperty->PropertyClass->GetAuthoredName(), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportObjectFromString", "Unable to import JSON string into {0} property {1}"), FText::FromString(*ObjectProperty->PropertyClass->GetAuthoredName()), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + } + else + { + // Default to expect a string for everything else + if (Property->ImportText_Direct(*JsonValue->AsString(), OutValue, nullptr, 0) == nullptr) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Unable to import JSON string into property %s"), *Property->GetAuthoredName()); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportFromString", "Unable to import JSON string into property {0}"), FText::FromString(Property->GetAuthoredName())); + } + return false; + } + } + + return true; + } + + + bool JsonValueToFPropertyWithContainer(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason) + { + if (!JsonValue.IsValid()) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Invalid JSON value")); + if (OutFailReason) + { + *OutFailReason = LOCTEXT("InvalidJsonValue", "Invalid JSON value"); + } + return false; + } + + const bool bArrayOrSetProperty = Property->IsA<FArrayProperty>() || Property->IsA<FSetProperty>(); + const bool bJsonArray = JsonValue->Type == EJson::Array; + + if (!bJsonArray) + { + if (bArrayOrSetProperty) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Expecting JSON array")); + if (OutFailReason) + { + *OutFailReason = LOCTEXT("ExpectingJsonArray", "Expecting JSON array"); + } + return false; + } + + if (Property->ArrayDim != 1) + { + if (bStrictMode) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - Property %s is not an array but has %d elements"), *Property->GetAuthoredName(), Property->ArrayDim); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("InvalidDimensionOfNonArrayProperty", "Property {0} is not an array but has {1} elements"), FText::FromString(Property->GetAuthoredName()), FText::AsNumber(Property->ArrayDim)); + } + return false; + } + + UE_LOG(LogJson, Warning, TEXT("Ignoring excess properties when deserializing %s"), *Property->GetAuthoredName()); + } + + return ConvertScalarJsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags, bStrictMode, OutFailReason); + } + + // In practice, the ArrayDim == 1 check ought to be redundant, since nested arrays of FProperties are not supported + if (bArrayOrSetProperty && Property->ArrayDim == 1) + { + // Read into TArray + return ConvertScalarJsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, ContainerStruct, Container, CheckFlags, SkipFlags, bStrictMode, OutFailReason); + } + + // We're deserializing a JSON array + const auto& ArrayValue = JsonValue->AsArray(); + + if (bStrictMode && (Property->ArrayDim != ArrayValue.Num())) + { + UE_LOG(LogJson, Error, TEXT("JsonValueToUProperty - JSON array size is incorrect (has %d elements, but needs %d)"), ArrayValue.Num(), Property->ArrayDim); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("IncorrectArraySize", "JSON array size is incorrect (has {0} elements, but needs {1})"), FText::AsNumber(ArrayValue.Num()), FText::AsNumber(Property->ArrayDim)); + } + return false; + } + + if (Property->ArrayDim < ArrayValue.Num()) + { + UE_LOG(LogJson, Warning, TEXT("Ignoring excess properties when deserializing %s"), *Property->GetAuthoredName()); + } + + // Read into native array + const int32 ItemsToRead = FMath::Clamp(ArrayValue.Num(), 0, Property->ArrayDim); + for (int Index = 0; Index != ItemsToRead; ++Index) + { + if (!ConvertScalarJsonValueToFPropertyWithContainer(ArrayValue[Index], Property, static_cast<char*>(OutValue) + Index * Property->ElementSize, ContainerStruct, Container, CheckFlags, SkipFlags, bStrictMode, OutFailReason)) + { + return false; + } + } + return true; + } + + bool JsonAttributesToUStructWithContainer(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, const UStruct* ContainerStruct, void* Container, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason) + { + if (StructDefinition == FJsonObjectWrapper::StaticStruct()) + { + // Just copy it into the object + FJsonObjectWrapper* ProxyObject = (FJsonObjectWrapper*)OutStruct; + ProxyObject->JsonObject = MakeShared<FJsonObject>(); + ProxyObject->JsonObject->Values = JsonAttributes; + return true; + } + + int32 NumUnclaimedProperties = JsonAttributes.Num(); + if (NumUnclaimedProperties <= 0) + { + return true; + } + +// ---------- UUserDefinedStruct ---------- + bool bUserStruct = StructDefinition->IsA( UUserDefinedStruct::StaticClass() ); +// ---------- UUserDefinedStruct ---------- + + // iterate over the struct properties + for (TFieldIterator<FProperty> PropIt(StructDefinition); PropIt; ++PropIt) + { + FProperty* Property = *PropIt; + + // Check to see if we should ignore this property + if (CheckFlags != 0 && !Property->HasAnyPropertyFlags(CheckFlags)) + { + continue; + } + if (Property->HasAnyPropertyFlags(SkipFlags)) + { + continue; + } + +// ---------- UUserDefinedStruct::GetAuthoredNameForField() ---------- + FString PropertyName = StructDefinition->GetAuthoredNameForField(Property); + if (bUserStruct) + { + const int32 GuidStrLen = 32; + const int32 MinimalPostfixlen = GuidStrLen + 3; + if (PropertyName.Len() > MinimalPostfixlen) + { + FString DisplayName = PropertyName.LeftChop(GuidStrLen + 1); + int FirstCharToRemove = -1; + const bool bCharFound = DisplayName.FindLastChar(TCHAR('_'), FirstCharToRemove); + if (bCharFound && (FirstCharToRemove > 0)) + { + PropertyName = DisplayName.Mid(0, FirstCharToRemove); + } + } + } +// ---------- UUserDefinedStruct::GetAuthoredNameForField() ---------- + + // find a json value matching this property name + const TSharedPtr<FJsonValue>* JsonValue = JsonAttributes.Find(PropertyName); + + if (!JsonValue) + { + if (bStrictMode) + { + UE_LOG(LogJson, Error, TEXT("JsonObjectToUStruct - Missing JSON value named %s"), *PropertyName); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("MissingJsonField", "Missing JSON value named {0}"), FText::FromString(PropertyName)); + } + return false; + } + + // we allow values to not be found since this mirrors the typical UObject mantra that all the fields are optional when deserializing + continue; + } + + if (JsonValue->IsValid() && !(*JsonValue)->IsNull()) + { + void* Value = Property->ContainerPtrToValuePtr<uint8>(OutStruct); + if (!JsonValueToFPropertyWithContainer(*JsonValue, Property, Value, ContainerStruct, Container, CheckFlags, SkipFlags, bStrictMode, OutFailReason)) + { + UE_LOG(LogJson, Error, TEXT("JsonObjectToUStruct - Unable to import JSON value into property %s"), *PropertyName); + if (OutFailReason) + { + *OutFailReason = FText::Format(LOCTEXT("FailImportValueToProperty", "Unable to import JSON value into property {0}\n{1}"), FText::FromString(PropertyName), *OutFailReason); + } + return false; + } + } + + if (--NumUnclaimedProperties <= 0) + { + // Should we log a warning/error if we still have properties in the JSON data that aren't in the struct definition in strict mode? + + // If we found all properties that were in the JsonAttributes map, there is no reason to keep looking for more. + break; + } + } + + return true; + } +} + +bool FJsonLibraryConverter::JsonValueToUProperty(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason) +{ + return JsonValueToFPropertyWithContainer(JsonValue, Property, OutValue, nullptr, nullptr, CheckFlags, SkipFlags, bStrictMode, OutFailReason); +} + +bool FJsonLibraryConverter::JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason) +{ + return JsonAttributesToUStruct(JsonObject->Values, StructDefinition, OutStruct, CheckFlags, SkipFlags, bStrictMode, OutFailReason); +} + +bool FJsonLibraryConverter::JsonAttributesToUStruct(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags, int64 SkipFlags, const bool bStrictMode, FText* OutFailReason) +{ + return JsonAttributesToUStructWithContainer(JsonAttributes, StructDefinition, OutStruct, StructDefinition, OutStruct, CheckFlags, SkipFlags, bStrictMode, OutFailReason); +} + +//static +bool FJsonLibraryConverter::GetTextFromField(const FString& FieldName, const TSharedPtr<FJsonValue>& FieldValue, FText& TextOut) +{ + if (FieldValue.IsValid()) + { + switch (FieldValue->Type) + { + case EJson::Number: + { + // number + TextOut = FText::AsNumber(FieldValue->AsNumber()); + return true; + } + case EJson::String: + { + if (FieldName.StartsWith(TEXT("date-"))) + { + FDateTime Dte; + if (FDateTime::ParseIso8601(*FieldValue->AsString(), Dte)) + { + TextOut = FText::AsDate(Dte); + return true; + } + } + else if (FieldName.StartsWith(TEXT("datetime-"))) + { + FDateTime Dte; + if (FDateTime::ParseIso8601(*FieldValue->AsString(), Dte)) + { + TextOut = FText::AsDateTime(Dte); + return true; + } + } + else + { + // culture invariant string + TextOut = FText::FromString(FieldValue->AsString()); + return true; + } + break; + } + case EJson::Object: + { + // localized string + if (FJsonLibraryConverter::GetTextFromObject(FieldValue->AsObject().ToSharedRef(), TextOut)) + { + return true; + } + + UE_LOG(LogJson, Error, TEXT("Unable to apply JSON parameter %s (could not parse object)"), *FieldName); + break; + } + default: + { + UE_LOG(LogJson, Error, TEXT("Unable to apply JSON parameter %s (bad type)"), *FieldName); + break; + } + } + } + return false; +} + +FFormatNamedArguments FJsonLibraryConverter::ParseTextArgumentsFromJson(const TSharedPtr<const FJsonObject>& JsonObject) +{ + FFormatNamedArguments NamedArgs; + if (JsonObject.IsValid()) + { + for (const auto& It : JsonObject->Values) + { + FText TextValue; + if (GetTextFromField(It.Key, It.Value, TextValue)) + { + NamedArgs.Emplace(It.Key, TextValue); + } + } + } + return NamedArgs; +} +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryHelpers.cpp b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryHelpers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ec0c46a3006ed9368545c5e4480a1572196959d2 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryHelpers.cpp @@ -0,0 +1,1795 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "JsonLibraryHelpers.h" + +FJsonLibraryValue UJsonLibraryHelpers::Parse( const FString& Text, bool bComments /*= false*/, bool bTrailingCommas /*= false*/ ) +{ + if ( bComments || bTrailingCommas ) + return FJsonLibraryValue::ParseRelaxed( Text, bComments, bTrailingCommas ); + + return FJsonLibraryValue::Parse( Text ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ParseObject( const FString& Text, const FJsonLibraryObjectNotify& Notify ) +{ + return FJsonLibraryObject::Parse( Text, Notify ); +} + +FJsonLibraryList UJsonLibraryHelpers::ParseList( const FString& Text, const FJsonLibraryListNotify& Notify ) +{ + return FJsonLibraryList::Parse( Text, Notify ); +} + +FJsonLibraryValue UJsonLibraryHelpers::ConstructNull() +{ + return FJsonLibraryValue(); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConstructObject( const FJsonLibraryObjectNotify& Notify ) +{ + return FJsonLibraryObject( Notify ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConstructList( const FJsonLibraryListNotify& Notify ) +{ + return FJsonLibraryList( Notify ); +} + +TArray<FJsonLibraryValue> UJsonLibraryHelpers::ConstructArray() +{ + return TArray<FJsonLibraryValue>(); +} + +TMap<FString, FJsonLibraryValue> UJsonLibraryHelpers::ConstructMap() +{ + return TMap<FString, FJsonLibraryValue>(); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromBoolean( bool Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromFloat( float Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromInteger( int32 Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromString( const FString& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromDateTime( const FDateTime& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromGuid( const FGuid& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromColor( const FColor& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromLinearColor( const FLinearColor& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromRotator( const FRotator& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromTransform( const FTransform& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromVector( const FVector& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromObject( const FJsonLibraryObject& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromList( const FJsonLibraryList& Value ) +{ + return FJsonLibraryValue( Value ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromArray( const TArray<FJsonLibraryValue>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromMap( const TMap<FString, FJsonLibraryValue>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromBooleanArray( const TArray<bool>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromFloatArray( const TArray<float>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromIntegerArray( const TArray<int32>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromStringArray( const TArray<FString>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromDateTimeArray( const TArray<FDateTime>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromGuidArray( const TArray<FGuid>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromColorArray( const TArray<FColor>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromLinearColorArray( const TArray<FLinearColor>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromRotatorArray( const TArray<FRotator>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromTransformArray( const TArray<FTransform>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromVectorArray( const TArray<FVector>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromObjectArray( const TArray<FJsonLibraryObject>& Value ) +{ + return FromList( FJsonLibraryList( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromBooleanMap( const TMap<FString, bool>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromFloatMap( const TMap<FString, float>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromIntegerMap( const TMap<FString, int32>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromStringMap( const TMap<FString, FString>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromDateTimeMap( const TMap<FString, FDateTime>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromGuidMap( const TMap<FString, FGuid>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromColorMap( const TMap<FString, FColor>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromLinearColorMap( const TMap<FString, FLinearColor>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromRotatorMap( const TMap<FString, FRotator>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromTransformMap( const TMap<FString, FTransform>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +FJsonLibraryValue UJsonLibraryHelpers::FromVectorMap( const TMap<FString, FVector>& Value ) +{ + return FromObject( FJsonLibraryObject( Value ) ); +} + +bool UJsonLibraryHelpers::ToBoolean( const FJsonLibraryValue& Value ) +{ + return Value.GetBoolean(); +} + +float UJsonLibraryHelpers::ToFloat( const FJsonLibraryValue& Value ) +{ + return Value.GetFloat(); +} + +int32 UJsonLibraryHelpers::ToInteger( const FJsonLibraryValue& Value ) +{ + return Value.GetInteger(); +} + +FString UJsonLibraryHelpers::ToString( const FJsonLibraryValue& Value ) +{ + return Value.GetString(); +} + +FDateTime UJsonLibraryHelpers::ToDateTime( const FJsonLibraryValue& Value ) +{ + return Value.GetDateTime(); +} + +FGuid UJsonLibraryHelpers::ToGuid( const FJsonLibraryValue& Value ) +{ + return Value.GetGuid(); +} + +FColor UJsonLibraryHelpers::ToColor( const FJsonLibraryValue& Value ) +{ + return Value.GetColor(); +} + +FLinearColor UJsonLibraryHelpers::ToLinearColor( const FJsonLibraryValue& Value ) +{ + return Value.GetLinearColor(); +} + +FRotator UJsonLibraryHelpers::ToRotator( const FJsonLibraryValue& Value ) +{ + return Value.GetRotator(); +} + +FTransform UJsonLibraryHelpers::ToTransform( const FJsonLibraryValue& Value ) +{ + return Value.GetTransform(); +} + +FVector UJsonLibraryHelpers::ToVector( const FJsonLibraryValue& Value ) +{ + return Value.GetVector(); +} + +FJsonLibraryObject UJsonLibraryHelpers::ToObject( const FJsonLibraryValue& Value ) +{ + return Value.GetObject(); +} + +FJsonLibraryList UJsonLibraryHelpers::ToList( const FJsonLibraryValue& Value ) +{ + return Value.GetList(); +} + +TArray<FJsonLibraryValue> UJsonLibraryHelpers::ToArray( const FJsonLibraryValue& Target ) +{ + return Target.ToArray(); +} + +TMap<FString, FJsonLibraryValue> UJsonLibraryHelpers::ToMap( const FJsonLibraryValue& Target ) +{ + return Target.ToMap(); +} + +TArray<bool> UJsonLibraryHelpers::ToBooleanArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToBooleanArray(); +} + +TArray<float> UJsonLibraryHelpers::ToFloatArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToFloatArray(); +} + +TArray<int32> UJsonLibraryHelpers::ToIntegerArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToIntegerArray(); +} + +TArray<FString> UJsonLibraryHelpers::ToStringArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToStringArray(); +} + +TArray<FDateTime> UJsonLibraryHelpers::ToDateTimeArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToDateTimeArray(); +} + +TArray<FGuid> UJsonLibraryHelpers::ToGuidArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToGuidArray(); +} + +TArray<FColor> UJsonLibraryHelpers::ToColorArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToColorArray(); +} + +TArray<FLinearColor> UJsonLibraryHelpers::ToLinearColorArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToLinearColorArray(); +} + +TArray<FRotator> UJsonLibraryHelpers::ToRotatorArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToRotatorArray(); +} + +TArray<FTransform> UJsonLibraryHelpers::ToTransformArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToTransformArray(); +} + +TArray<FVector> UJsonLibraryHelpers::ToVectorArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToVectorArray(); +} + +TArray<FJsonLibraryObject> UJsonLibraryHelpers::ToObjectArray( const FJsonLibraryValue& Target ) +{ + return Target.GetList().ToObjectArray(); +} + +TMap<FString, bool> UJsonLibraryHelpers::ToBooleanMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToBooleanMap(); +} + +TMap<FString, float> UJsonLibraryHelpers::ToFloatMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToFloatMap(); +} + +TMap<FString, int32> UJsonLibraryHelpers::ToIntegerMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToIntegerMap(); +} + +TMap<FString, FString> UJsonLibraryHelpers::ToStringMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToStringMap(); +} + +TMap<FString, FDateTime> UJsonLibraryHelpers::ToDateTimeMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToDateTimeMap(); +} + +TMap<FString, FGuid> UJsonLibraryHelpers::ToGuidMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToGuidMap(); +} + +TMap<FString, FColor> UJsonLibraryHelpers::ToColorMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToColorMap(); +} + +TMap<FString, FLinearColor> UJsonLibraryHelpers::ToLinearColorMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToLinearColorMap(); +} + +TMap<FString, FRotator> UJsonLibraryHelpers::ToRotatorMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToRotatorMap(); +} + +TMap<FString, FTransform> UJsonLibraryHelpers::ToTransformMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToTransformMap(); +} + +TMap<FString, FVector> UJsonLibraryHelpers::ToVectorMap( const FJsonLibraryValue& Target ) +{ + return Target.GetObject().ToVectorMap(); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertLinearColorToObject( const FLinearColor& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertRotatorToObject( const FRotator& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertTransformToObject( const FTransform& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertVectorToObject( const FVector& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FLinearColor UJsonLibraryHelpers::ConvertObjectToLinearColor( const FJsonLibraryObject& Object ) +{ + return Object.ToLinearColor(); +} + +FRotator UJsonLibraryHelpers::ConvertObjectToRotator( const FJsonLibraryObject& Object ) +{ + return Object.ToRotator(); +} + +FTransform UJsonLibraryHelpers::ConvertObjectToTransform( const FJsonLibraryObject& Object ) +{ + return Object.ToTransform(); +} + +FVector UJsonLibraryHelpers::ConvertObjectToVector( const FJsonLibraryObject& Object ) +{ + return Object.ToVector(); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertMapToObject( const TMap<FString, FJsonLibraryValue>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +TMap<FString, FJsonLibraryValue> UJsonLibraryHelpers::ConvertObjectToMap( const FJsonLibraryObject& Object ) +{ + return Object.ToMap(); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertBooleanMapToObject( const TMap<FString, bool>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertFloatMapToObject( const TMap<FString, float>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertIntegerMapToObject( const TMap<FString, int32>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertStringMapToObject( const TMap<FString, FString>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertDateTimeMapToObject( const TMap<FString, FDateTime>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertGuidMapToObject( const TMap<FString, FGuid>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertColorMapToObject( const TMap<FString, FColor>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertLinearColorMapToObject( const TMap<FString, FLinearColor>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertRotatorMapToObject( const TMap<FString, FRotator>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertTransformMapToObject( const TMap<FString, FTransform>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryObject UJsonLibraryHelpers::ConvertVectorMapToObject( const TMap<FString, FVector>& Value ) +{ + return FJsonLibraryObject( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertArrayToList( const TArray<FJsonLibraryValue>& Value ) +{ + return FJsonLibraryList( Value ); +} + +TArray<FJsonLibraryValue> UJsonLibraryHelpers::ConvertListToArray( const FJsonLibraryList& List ) +{ + return List.ToArray(); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertBooleanArrayToList( const TArray<bool>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertFloatArrayToList( const TArray<float>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertIntegerArrayToList( const TArray<int32>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertStringArrayToList( const TArray<FString>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertDateTimeArrayToList( const TArray<FDateTime>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertGuidArrayToList( const TArray<FGuid>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertColorArrayToList( const TArray<FColor>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertLinearColorArrayToList( const TArray<FLinearColor>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertRotatorArrayToList( const TArray<FRotator>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertTransformArrayToList( const TArray<FTransform>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertVectorArrayToList( const TArray<FVector>& Value ) +{ + return FJsonLibraryList( Value ); +} + +FJsonLibraryList UJsonLibraryHelpers::ConvertObjectArrayToList( const TArray<FJsonLibraryObject>& Value ) +{ + return FJsonLibraryList( Value ); +} + + +EJsonLibraryType UJsonLibraryHelpers::JsonValue_GetType( const FJsonLibraryValue& Target ) +{ + return Target.GetType(); +} + +bool UJsonLibraryHelpers::JsonValue_Equals( const FJsonLibraryValue& Target, const FJsonLibraryValue& Value ) +{ + return Target.Equals( Value ); +} + +bool UJsonLibraryHelpers::JsonValue_IsValid( const FJsonLibraryValue& Target ) +{ + return Target.IsValid(); +} + +bool UJsonLibraryHelpers::JsonValue_IsGuid( const FJsonLibraryValue& Target ) +{ + return Target.IsGuid(); +} + +bool UJsonLibraryHelpers::JsonValue_IsRotator( const FJsonLibraryValue& Target ) +{ + return Target.IsRotator(); +} + +bool UJsonLibraryHelpers::JsonValue_IsTransform( const FJsonLibraryValue& Target ) +{ + return Target.IsTransform(); +} + +bool UJsonLibraryHelpers::JsonValue_IsVector( const FJsonLibraryValue& Target ) +{ + return Target.IsVector(); +} + +FString UJsonLibraryHelpers::JsonValue_Stringify( const FJsonLibraryValue& Target, bool bCondensed /*= true*/ ) +{ + return Target.Stringify( bCondensed ); +} + + +bool UJsonLibraryHelpers::JsonObject_Equals( const FJsonLibraryObject& Target, const FJsonLibraryObject& Object ) +{ + return Target.Equals( Object ); +} + +int32 UJsonLibraryHelpers::JsonObject_Count( const FJsonLibraryObject& Target ) +{ + return Target.Count(); +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_Clear( FJsonLibraryObject& Target ) +{ + Target.Clear(); + return Target; +} + +bool UJsonLibraryHelpers::JsonObject_HasKey( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.HasKey( Key ); +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_RemoveKey( FJsonLibraryObject& Target, const FString& Key ) +{ + Target.RemoveKey( Key ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_Add( FJsonLibraryObject& Target, const FJsonLibraryObject& Object ) +{ + Target.Add( Object ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddBooleanMap( FJsonLibraryObject& Target, const TMap<FString, bool>& Map ) +{ + Target.AddBooleanMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddFloatMap( FJsonLibraryObject& Target, const TMap<FString, float>& Map ) +{ + Target.AddFloatMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddIntegerMap( FJsonLibraryObject& Target, const TMap<FString, int32>& Map ) +{ + Target.AddIntegerMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddStringMap( FJsonLibraryObject& Target, const TMap<FString, FString>& Map ) +{ + Target.AddStringMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddDateTimeMap( FJsonLibraryObject& Target, const TMap<FString, FDateTime>& Map ) +{ + Target.AddDateTimeMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddGuidMap( FJsonLibraryObject& Target, const TMap<FString, FGuid>& Map ) +{ + Target.AddGuidMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddColorMap( FJsonLibraryObject& Target, const TMap<FString, FColor>& Map ) +{ + Target.AddColorMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddLinearColorMap( FJsonLibraryObject& Target, const TMap<FString, FLinearColor>& Map ) +{ + Target.AddLinearColorMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddRotatorMap( FJsonLibraryObject& Target, const TMap<FString, FRotator>& Map ) +{ + Target.AddRotatorMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddTransformMap( FJsonLibraryObject& Target, const TMap<FString, FTransform>& Map ) +{ + Target.AddTransformMap( Map ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_AddVectorMap( FJsonLibraryObject& Target, const TMap<FString, FVector>& Map ) +{ + Target.AddVectorMap( Map ); + return Target; +} + +TArray<FString> UJsonLibraryHelpers::JsonObject_GetKeys( const FJsonLibraryObject& Target ) +{ + return Target.GetKeys(); +} + +TArray<FJsonLibraryValue> UJsonLibraryHelpers::JsonObject_GetValues( const FJsonLibraryObject& Target ) +{ + return Target.GetValues(); +} + +bool UJsonLibraryHelpers::JsonObject_GetBoolean( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetBoolean( Key ); +} + +float UJsonLibraryHelpers::JsonObject_GetFloat( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetFloat( Key ); +} + +int32 UJsonLibraryHelpers::JsonObject_GetInteger( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetInteger( Key ); +} + +FString UJsonLibraryHelpers::JsonObject_GetString( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetString( Key ); +} + +FDateTime UJsonLibraryHelpers::JsonObject_GetDateTime( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetDateTime( Key ); +} + +FGuid UJsonLibraryHelpers::JsonObject_GetGuid( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetGuid( Key ); +} + +FColor UJsonLibraryHelpers::JsonObject_GetColor( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetColor( Key ); +} + +FLinearColor UJsonLibraryHelpers::JsonObject_GetLinearColor( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetLinearColor( Key ); +} + +FRotator UJsonLibraryHelpers::JsonObject_GetRotator( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetRotator( Key ); +} + +FTransform UJsonLibraryHelpers::JsonObject_GetTransform( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetTransform( Key ); +} + +FVector UJsonLibraryHelpers::JsonObject_GetVector( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetVector( Key ); +} + +FJsonLibraryValue UJsonLibraryHelpers::JsonObject_GetValue( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetValue( Key ); +} + +FJsonLibraryObject UJsonLibraryHelpers::JsonObject_GetObject( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetObject( Key ); +} + +FJsonLibraryList UJsonLibraryHelpers::JsonObject_GetList( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetList( Key ); +} + +TArray<FJsonLibraryValue> UJsonLibraryHelpers::JsonObject_GetArray( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetArray( Key ); +} + +TMap<FString, FJsonLibraryValue> UJsonLibraryHelpers::JsonObject_GetMap( const FJsonLibraryObject& Target, const FString& Key ) +{ + return Target.GetMap( Key ); +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetBoolean( FJsonLibraryObject& Target, const FString& Key, bool Value ) +{ + Target.SetBoolean( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetFloat( FJsonLibraryObject& Target, const FString& Key, float Value ) +{ + Target.SetFloat( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetInteger( FJsonLibraryObject& Target, const FString& Key, int32 Value ) +{ + Target.SetInteger( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetString( FJsonLibraryObject& Target, const FString& Key, const FString& Value ) +{ + Target.SetString( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetDateTime( FJsonLibraryObject& Target, const FString& Key, const FDateTime& Value ) +{ + Target.SetDateTime( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetGuid( FJsonLibraryObject& Target, const FString& Key, const FGuid& Value ) +{ + Target.SetGuid( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetColor( FJsonLibraryObject& Target, const FString& Key, const FColor& Value ) +{ + Target.SetColor( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetLinearColor( FJsonLibraryObject& Target, const FString& Key, const FLinearColor& Value ) +{ + Target.SetLinearColor( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetRotator( FJsonLibraryObject& Target, const FString& Key, const FRotator& Value ) +{ + Target.SetRotator( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetTransform( FJsonLibraryObject& Target, const FString& Key, const FTransform& Value ) +{ + Target.SetTransform( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetVector( FJsonLibraryObject& Target, const FString& Key, const FVector& Value ) +{ + Target.SetVector( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetValue( FJsonLibraryObject& Target, const FString& Key, const FJsonLibraryValue& Value ) +{ + Target.SetValue( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetObject( FJsonLibraryObject& Target, const FString& Key, const FJsonLibraryObject& Value ) +{ + Target.SetObject( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetList( FJsonLibraryObject& Target, const FString& Key, const FJsonLibraryList& Value ) +{ + Target.SetList( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetArray( FJsonLibraryObject& Target, const FString& Key, const TArray<FJsonLibraryValue>& Value ) +{ + Target.SetArray( Key, Value ); + return Target; +} + +FJsonLibraryObject& UJsonLibraryHelpers::JsonObject_SetMap( FJsonLibraryObject& Target, const FString& Key, const TMap<FString, FJsonLibraryValue>& Value ) +{ + Target.SetMap( Key, Value ); + return Target; +} + +bool UJsonLibraryHelpers::JsonObject_IsValid( const FJsonLibraryObject& Target ) +{ + return Target.IsValid(); +} + +bool UJsonLibraryHelpers::JsonObject_IsEmpty( const FJsonLibraryObject& Target ) +{ + return Target.IsEmpty(); +} + +bool UJsonLibraryHelpers::JsonObject_IsRotator( const FJsonLibraryObject& Target ) +{ + return Target.IsRotator(); +} + +bool UJsonLibraryHelpers::JsonObject_IsTransform( const FJsonLibraryObject& Target ) +{ + return Target.IsTransform(); +} + +bool UJsonLibraryHelpers::JsonObject_IsVector( const FJsonLibraryObject& Target ) +{ + return Target.IsVector(); +} + +FString UJsonLibraryHelpers::JsonObject_Stringify( const FJsonLibraryObject& Target, bool bCondensed /*= true*/ ) +{ + return Target.Stringify( bCondensed ); +} + + +bool UJsonLibraryHelpers::JsonList_Equals( const FJsonLibraryList& Target, const FJsonLibraryList& List ) +{ + return Target.Equals( List ); +} + +int32 UJsonLibraryHelpers::JsonList_Count( const FJsonLibraryList& Target ) +{ + return Target.Count(); +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_Clear( FJsonLibraryList& Target ) +{ + Target.Clear(); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_Swap( FJsonLibraryList& Target, int32 IndexA, int32 IndexB ) +{ + Target.Swap( IndexA, IndexB ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_Append( FJsonLibraryList& Target, const FJsonLibraryList& List ) +{ + Target.Append( List ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendBooleanArray( FJsonLibraryList& Target, const TArray<bool>& Array ) +{ + Target.AppendBooleanArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendFloatArray( FJsonLibraryList& Target, const TArray<float>& Array ) +{ + Target.AppendFloatArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendIntegerArray( FJsonLibraryList& Target, const TArray<int32>& Array ) +{ + Target.AppendIntegerArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendStringArray( FJsonLibraryList& Target, const TArray<FString>& Array ) +{ + Target.AppendStringArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendDateTimeArray( FJsonLibraryList& Target, const TArray<FDateTime>& Array ) +{ + Target.AppendDateTimeArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendGuidArray( FJsonLibraryList& Target, const TArray<FGuid>& Array ) +{ + Target.AppendGuidArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendColorArray( FJsonLibraryList& Target, const TArray<FColor>& Array ) +{ + Target.AppendColorArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendLinearColorArray( FJsonLibraryList& Target, const TArray<FLinearColor>& Array ) +{ + Target.AppendLinearColorArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendRotatorArray( FJsonLibraryList& Target, const TArray<FRotator>& Array ) +{ + Target.AppendRotatorArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendTransformArray( FJsonLibraryList& Target, const TArray<FTransform>& Array ) +{ + Target.AppendTransformArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendVectorArray( FJsonLibraryList& Target, const TArray<FVector>& Array ) +{ + Target.AppendVectorArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AppendObjectArray( FJsonLibraryList& Target, const TArray<FJsonLibraryObject>& Array ) +{ + Target.AppendObjectArray( Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_Inject( FJsonLibraryList& Target, int32 Index, const FJsonLibraryList& List ) +{ + Target.Inject( Index, List ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectBooleanArray( FJsonLibraryList& Target, int32 Index, const TArray<bool>& Array ) +{ + Target.InjectBooleanArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectFloatArray( FJsonLibraryList& Target, int32 Index, const TArray<float>& Array ) +{ + Target.InjectFloatArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectIntegerArray( FJsonLibraryList& Target, int32 Index, const TArray<int32>& Array ) +{ + Target.InjectIntegerArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectStringArray( FJsonLibraryList& Target, int32 Index, const TArray<FString>& Array ) +{ + Target.InjectStringArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectDateTimeArray( FJsonLibraryList& Target, int32 Index, const TArray<FDateTime>& Array ) +{ + Target.InjectDateTimeArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectGuidArray( FJsonLibraryList& Target, int32 Index, const TArray<FGuid>& Array ) +{ + Target.InjectGuidArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectColorArray( FJsonLibraryList& Target, int32 Index, const TArray<FColor>& Array ) +{ + Target.InjectColorArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectLinearColorArray( FJsonLibraryList& Target, int32 Index, const TArray<FLinearColor>& Array ) +{ + Target.InjectLinearColorArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectRotatorArray( FJsonLibraryList& Target, int32 Index, const TArray<FRotator>& Array ) +{ + Target.InjectRotatorArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectTransformArray( FJsonLibraryList& Target, int32 Index, const TArray<FTransform>& Array ) +{ + Target.InjectTransformArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectVectorArray( FJsonLibraryList& Target, int32 Index, const TArray<FVector>& Array ) +{ + Target.InjectVectorArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InjectObjectArray( FJsonLibraryList& Target, int32 Index, const TArray<FJsonLibraryObject>& Array ) +{ + Target.InjectObjectArray( Index, Array ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddBoolean( FJsonLibraryList& Target, bool Value ) +{ + Target.AddBoolean( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddFloat( FJsonLibraryList& Target, float Value ) +{ + Target.AddFloat( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddInteger( FJsonLibraryList& Target, int32 Value ) +{ + Target.AddInteger( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddString( FJsonLibraryList& Target, const FString& Value ) +{ + Target.AddString( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddDateTime( FJsonLibraryList& Target, const FDateTime& Value ) +{ + Target.AddDateTime( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddGuid( FJsonLibraryList& Target, const FGuid& Value ) +{ + Target.AddGuid( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddColor( FJsonLibraryList& Target, const FColor& Value ) +{ + Target.AddColor( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddLinearColor( FJsonLibraryList& Target, const FLinearColor& Value ) +{ + Target.AddLinearColor( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddRotator( FJsonLibraryList& Target, const FRotator& Value ) +{ + Target.AddRotator( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddTransform( FJsonLibraryList& Target, const FTransform& Value ) +{ + Target.AddTransform( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddVector( FJsonLibraryList& Target, const FVector& Value ) +{ + Target.AddVector( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddValue( FJsonLibraryList& Target, const FJsonLibraryValue& Value ) +{ + Target.AddValue( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddObject( FJsonLibraryList& Target, const FJsonLibraryObject& Value ) +{ + Target.AddObject( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddList( FJsonLibraryList& Target, const FJsonLibraryList& Value ) +{ + Target.AddList( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddArray( FJsonLibraryList& Target, const TArray<FJsonLibraryValue>& Value ) +{ + Target.AddArray( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_AddMap( FJsonLibraryList& Target, const TMap<FString, FJsonLibraryValue>& Value ) +{ + Target.AddMap( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertBoolean( FJsonLibraryList& Target, int32 Index, bool Value ) +{ + Target.InsertBoolean( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertFloat( FJsonLibraryList& Target, int32 Index, float Value ) +{ + Target.InsertFloat( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertInteger( FJsonLibraryList& Target, int32 Index, int32 Value ) +{ + Target.InsertInteger( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertString( FJsonLibraryList& Target, int32 Index, const FString& Value ) +{ + Target.InsertString( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertDateTime( FJsonLibraryList& Target, int32 Index, const FDateTime& Value ) +{ + Target.InsertDateTime( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertGuid( FJsonLibraryList& Target, int32 Index, const FGuid& Value ) +{ + Target.InsertGuid( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertColor( FJsonLibraryList& Target, int32 Index, const FColor& Value ) +{ + Target.InsertColor( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertLinearColor( FJsonLibraryList& Target, int32 Index, const FLinearColor& Value ) +{ + Target.InsertLinearColor( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertRotator( FJsonLibraryList& Target, int32 Index, const FRotator& Value ) +{ + Target.InsertRotator( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertTransform( FJsonLibraryList& Target, int32 Index, const FTransform& Value ) +{ + Target.InsertTransform( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertVector( FJsonLibraryList& Target, int32 Index, const FVector& Value ) +{ + Target.InsertVector( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertValue( FJsonLibraryList& Target, int32 Index, const FJsonLibraryValue& Value ) +{ + Target.InsertValue( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertObject( FJsonLibraryList& Target, int32 Index, const FJsonLibraryObject& Value ) +{ + Target.InsertObject( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertList( FJsonLibraryList& Target, int32 Index, const FJsonLibraryList& Value ) +{ + Target.InsertList( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertArray( FJsonLibraryList& Target, int32 Index, const TArray<FJsonLibraryValue>& Value ) +{ + Target.InsertArray( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_InsertMap( FJsonLibraryList& Target, int32 Index, const TMap<FString, FJsonLibraryValue>& Value ) +{ + Target.InsertMap( Index, Value ); + return Target; +} + +bool UJsonLibraryHelpers::JsonList_GetBoolean( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetBoolean( Index ); +} + +float UJsonLibraryHelpers::JsonList_GetFloat( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetFloat( Index ); +} + +int32 UJsonLibraryHelpers::JsonList_GetInteger( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetInteger( Index ); +} + +FString UJsonLibraryHelpers::JsonList_GetString( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetString( Index ); +} + +FDateTime UJsonLibraryHelpers::JsonList_GetDateTime( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetDateTime( Index ); +} + +FGuid UJsonLibraryHelpers::JsonList_GetGuid( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetGuid( Index ); +} + +FColor UJsonLibraryHelpers::JsonList_GetColor( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetColor( Index ); +} + +FLinearColor UJsonLibraryHelpers::JsonList_GetLinearColor( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetLinearColor( Index ); +} + +FRotator UJsonLibraryHelpers::JsonList_GetRotator( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetRotator( Index ); +} + +FTransform UJsonLibraryHelpers::JsonList_GetTransform( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetTransform( Index ); +} + +FVector UJsonLibraryHelpers::JsonList_GetVector( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetVector( Index ); +} + +FJsonLibraryValue UJsonLibraryHelpers::JsonList_GetValue( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetValue( Index ); +} + +FJsonLibraryObject UJsonLibraryHelpers::JsonList_GetObject( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetObject( Index ); +} + +FJsonLibraryList UJsonLibraryHelpers::JsonList_GetList( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetList( Index ); +} + +TArray<FJsonLibraryValue> UJsonLibraryHelpers::JsonList_GetArray( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetArray( Index ); +} + +TMap<FString, FJsonLibraryValue> UJsonLibraryHelpers::JsonList_GetMap( const FJsonLibraryList& Target, int32 Index ) +{ + return Target.GetMap( Index ); +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetBoolean( FJsonLibraryList& Target, int32 Index, bool Value ) +{ + Target.SetBoolean( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetFloat( FJsonLibraryList& Target, int32 Index, float Value ) +{ + Target.SetFloat( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetInteger( FJsonLibraryList& Target, int32 Index, int32 Value ) +{ + Target.SetInteger( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetString( FJsonLibraryList& Target, int32 Index, const FString& Value ) +{ + Target.SetString( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetDateTime( FJsonLibraryList& Target, int32 Index, const FDateTime& Value ) +{ + Target.SetDateTime( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetGuid( FJsonLibraryList& Target, int32 Index, const FGuid& Value ) +{ + Target.SetGuid( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetColor( FJsonLibraryList& Target, int32 Index, const FColor& Value ) +{ + Target.SetColor( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetLinearColor( FJsonLibraryList& Target, int32 Index, const FLinearColor& Value ) +{ + Target.SetLinearColor( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetRotator( FJsonLibraryList& Target, int32 Index, const FRotator& Value ) +{ + Target.SetRotator( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetTransform( FJsonLibraryList& Target, int32 Index, const FTransform& Value ) +{ + Target.SetTransform( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetVector( FJsonLibraryList& Target, int32 Index, const FVector& Value ) +{ + Target.SetVector( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetValue( FJsonLibraryList& Target, int32 Index, const FJsonLibraryValue& Value ) +{ + Target.SetValue( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetObject( FJsonLibraryList& Target, int32 Index, const FJsonLibraryObject& Value ) +{ + Target.SetObject( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetList( FJsonLibraryList& Target, int32 Index, const FJsonLibraryList& Value ) +{ + Target.SetList( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetArray( FJsonLibraryList& Target, int32 Index, const TArray<FJsonLibraryValue>& Value ) +{ + Target.SetArray( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_SetMap( FJsonLibraryList& Target, int32 Index, const TMap<FString, FJsonLibraryValue>& Value ) +{ + Target.SetMap( Index, Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_Remove( FJsonLibraryList& Target, int32 Index ) +{ + Target.Remove( Index ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveBoolean( FJsonLibraryList& Target, bool Value ) +{ + Target.RemoveBoolean( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveFloat( FJsonLibraryList& Target, float Value ) +{ + Target.RemoveFloat( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveInteger( FJsonLibraryList& Target, int32 Value ) +{ + Target.RemoveInteger( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveString( FJsonLibraryList& Target, const FString& Value ) +{ + Target.RemoveString( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveDateTime( FJsonLibraryList& Target, const FDateTime& Value ) +{ + Target.RemoveDateTime( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveGuid( FJsonLibraryList& Target, const FGuid& Value ) +{ + Target.RemoveGuid( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveColor( FJsonLibraryList& Target, const FColor& Value ) +{ + Target.RemoveColor( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveLinearColor( FJsonLibraryList& Target, const FLinearColor& Value ) +{ + Target.RemoveLinearColor( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveRotator( FJsonLibraryList& Target, const FRotator& Value ) +{ + Target.RemoveRotator( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveTransform( FJsonLibraryList& Target, const FTransform& Value ) +{ + Target.RemoveTransform( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveVector( FJsonLibraryList& Target, const FVector& Value ) +{ + Target.RemoveVector( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveValue( FJsonLibraryList& Target, const FJsonLibraryValue& Value ) +{ + Target.RemoveValue( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveObject( FJsonLibraryList& Target, const FJsonLibraryObject& Value ) +{ + Target.RemoveObject( Value ); + return Target; +} + +FJsonLibraryList& UJsonLibraryHelpers::JsonList_RemoveList( FJsonLibraryList& Target, const FJsonLibraryList& Value ) +{ + Target.RemoveList( Value ); + return Target; +} + +int32 UJsonLibraryHelpers::JsonList_FindBoolean( const FJsonLibraryList& Target, bool Value, int32 Index /*= 0*/ ) +{ + return Target.FindBoolean( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindFloat( const FJsonLibraryList& Target, float Value, int32 Index /*= 0*/ ) +{ + return Target.FindFloat( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindInteger( const FJsonLibraryList& Target, int32 Value, int32 Index /*= 0*/ ) +{ + return Target.FindInteger( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindString( const FJsonLibraryList& Target, const FString& Value, int32 Index /*= 0*/ ) +{ + return Target.FindString( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindDateTime( const FJsonLibraryList& Target, const FDateTime& Value, int32 Index /*= 0*/ ) +{ + return Target.FindDateTime( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindGuid( const FJsonLibraryList& Target, const FGuid& Value, int32 Index /*= 0*/ ) +{ + return Target.FindGuid( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindColor( const FJsonLibraryList& Target, const FColor& Value, int32 Index /*= 0*/ ) +{ + return Target.FindColor( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindLinearColor( const FJsonLibraryList& Target, const FLinearColor& Value, int32 Index /*= 0*/ ) +{ + return Target.FindLinearColor( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindRotator( const FJsonLibraryList& Target, const FRotator& Value, int32 Index /*= 0*/ ) +{ + return Target.FindRotator( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindTransform( const FJsonLibraryList& Target, const FTransform& Value, int32 Index /*= 0*/ ) +{ + return Target.FindTransform( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindVector( const FJsonLibraryList& Target, const FVector& Value, int32 Index /*= 0*/ ) +{ + return Target.FindVector( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindValue( const FJsonLibraryList& Target, const FJsonLibraryValue& Value, int32 Index /*= 0*/ ) +{ + return Target.FindValue( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindObject( const FJsonLibraryList& Target, const FJsonLibraryObject& Value, int32 Index /*= 0*/ ) +{ + return Target.FindObject( Value, Index ); +} + +int32 UJsonLibraryHelpers::JsonList_FindList( const FJsonLibraryList& Target, const FJsonLibraryList& Value, int32 Index /*= 0*/ ) +{ + return Target.FindList( Value, Index ); +} + +bool UJsonLibraryHelpers::JsonList_IsValid( const FJsonLibraryList& Target ) +{ + return Target.IsValid(); +} + +bool UJsonLibraryHelpers::JsonList_IsEmpty( const FJsonLibraryList& Target ) +{ + return Target.IsEmpty(); +} + +FString UJsonLibraryHelpers::JsonList_Stringify( const FJsonLibraryList& Target, bool bCondensed /*= true*/ ) +{ + return Target.Stringify( bCondensed ); +} + +FString UJsonLibraryHelpers::StripCommentsOrCommas( const FString& Text, bool bComments /*= true*/, bool bTrailingCommas /*= true*/ ) +{ + if ( !bComments && !bTrailingCommas ) + return Text; + + int32 BlockComment = -1; + int32 LineComment = -1; + int32 TrailingComma = -1; + + bool bStringLiteral = false; + bool bEscapeCharacter = false; + + int32 Length = Text.Len(); + FString StrippedText = Text; + for ( int32 Index = 0; Index < Length; Index++ ) + { + auto StrippedCharacter = StrippedText[ Index ]; + if ( BlockComment >= 0 ) + { + if ( StrippedCharacter == '*' && Index + 1 < Length && StrippedText[ Index + 1 ] == '/' ) + { + if ( bComments ) + { + StrippedText = Index + 2 < Length ? + StrippedText.Left( BlockComment ) + StrippedText.RightChop( Index + 2 ) : + StrippedText.Left( BlockComment ); + + int32 CommentLength = Index + 2 - BlockComment; + Length -= CommentLength; + Index -= CommentLength - 1; + } + + BlockComment = -1; + } + + continue; + } + else if ( LineComment >= 0 ) + { + if ( StrippedCharacter == '\r' || StrippedCharacter == '\n' ) + { + if ( bComments ) + { + StrippedText = StrippedText.Left( LineComment ) + StrippedText.RightChop( Index ); + + int32 CommentLength = Index - LineComment; + Length -= CommentLength; + Index -= CommentLength; + } + + LineComment = -1; + } + + continue; + } + else if ( !bStringLiteral && StrippedCharacter == '/' && Index + 1 < Length ) + { + if ( StrippedText[ Index + 1 ] == '*' ) + { + BlockComment = Index; + ++Index; + } + else if ( StrippedText[ Index + 1 ] == '/' ) + { + LineComment = Index; + ++Index; + } + else + TrailingComma = -1; + + bEscapeCharacter = false; + continue; + } + else if ( !bStringLiteral && TrailingComma >= 0 && ( StrippedCharacter == '}' + || StrippedCharacter == ']' ) ) + { + if ( bTrailingCommas ) + { + StrippedText = StrippedText.Left( TrailingComma ) + StrippedText.RightChop( TrailingComma + 1 ); + + Length -= 1; + Index -= 1; + } + + TrailingComma = -1; + bEscapeCharacter = false; + continue; + } + + if ( bStringLiteral ) + TrailingComma = -1; + else if ( StrippedCharacter == ',' ) + TrailingComma = Index; + else if ( !FChar::IsWhitespace( StrippedCharacter ) && StrippedCharacter != '\r' + && StrippedCharacter != '\n') + TrailingComma = -1; + + if ( !bEscapeCharacter ) + { + if ( StrippedCharacter == '"' ) + bStringLiteral = !bStringLiteral; + else if ( StrippedCharacter == '\\' ) + bEscapeCharacter = true; + } + else + bEscapeCharacter = false; + } + + if ( BlockComment >= 0 ) + StrippedText = StrippedText.Left( BlockComment ); + else if ( LineComment >= 0 ) + StrippedText = StrippedText.Left( LineComment ); + + return StrippedText; +} diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryList.cpp b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryList.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6e6a110e01bceaccdcd2715ef0898ddcd99ea9ce --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryList.cpp @@ -0,0 +1,1699 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "JsonLibraryList.h" +#include "JsonLibraryObject.h" +#include "JsonLibraryHelpers.h" +#include "Policies/CondensedJsonPrintPolicy.h" +#include "Policies/PrettyJsonPrintPolicy.h" + +FJsonLibraryList::FJsonLibraryList( const TSharedPtr<FJsonValue>& Value ) +{ + if ( Value.IsValid() && Value->Type == EJson::Array ) + JsonArray = StaticCastSharedPtr<FJsonValueArray>( Value ); +} + +FJsonLibraryList::FJsonLibraryList( const TSharedPtr<FJsonValueArray>& Value ) +{ + JsonArray = Value; +} + +FJsonLibraryList::FJsonLibraryList() +{ + JsonArray = MakeShareable( new FJsonValueArray( TArray<TSharedPtr<FJsonValue>>() ) ); +} + +FJsonLibraryList::FJsonLibraryList( const FJsonLibraryListNotify& Notify ) + : FJsonLibraryList() +{ + OnNotify = Notify; +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FJsonLibraryValue>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( Value[ i ].JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<bool>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<float>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<double>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<int32>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FString>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FDateTime>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FGuid>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FColor>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FLinearColor>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryValue( Value[ i ] ).JsonValue ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FRotator>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryObject( Value[ i ] ).JsonObject ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FTransform>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryObject( Value[ i ] ).JsonObject ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FVector>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( FJsonLibraryObject( Value[ i ] ).JsonObject ); + } +} + +FJsonLibraryList::FJsonLibraryList( const TArray<FJsonLibraryObject>& Value ) + : FJsonLibraryList() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( Json ) + { + for ( int32 i = 0; i < Value.Num(); i++ ) + Json->Add( Value[ i ].JsonObject ); + } +} + +bool FJsonLibraryList::Equals( const FJsonLibraryList& List ) const +{ + if ( !JsonArray.IsValid() || !List.JsonArray.IsValid() ) + return false; + + if ( JsonArray == List.JsonArray ) + return true; + + const TArray<TSharedPtr<FJsonValue>>* JsonA = GetJsonArray(); + const TArray<TSharedPtr<FJsonValue>>* JsonB = List.GetJsonArray(); + if ( JsonA && JsonB ) + return JsonA == JsonB; + + return false; +} + +int32 FJsonLibraryList::Count() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return 0; + + return Json->Num(); +} + +void FJsonLibraryList::Clear() +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + NotifyCheck(); + Json->Empty(); + NotifyClear(); +} + +void FJsonLibraryList::Swap( int32 IndexA, int32 IndexB ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + if ( IndexA >= 0 && IndexA < Json->Num() && IndexB >= 0 && IndexB < Json->Num() ) + { + if ( OnNotify.IsBound() ) + { + const TSharedPtr<FJsonValue>& ValueA = ( *Json )[ IndexA ]; + const TSharedPtr<FJsonValue>& ValueB = ( *Json )[ IndexB ]; + + NotifyCheck( IndexA ); + ( *Json )[ IndexA ] = ValueB; + NotifyChange( IndexA, FJsonLibraryValue( ValueB ) ); + + NotifyCheck( IndexB ); + ( *Json )[ IndexB ] = ValueA; + NotifyChange( IndexB, FJsonLibraryValue( ValueA ) ); + } + else + Json->Swap( IndexA, IndexB ); + } +} + +void FJsonLibraryList::Append( const FJsonLibraryList& List ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + const TArray<TSharedPtr<FJsonValue>>* ListJson = List.GetJsonArray(); + if ( !ListJson ) + return; + + if ( OnNotify.IsBound() ) + { + for ( int32 i = 0; i < ListJson->Num(); i++ ) + AddValue( FJsonLibraryValue( ( *ListJson )[ i ] ) ); + } + else + Json->Append( *ListJson ); +} + +void FJsonLibraryList::AppendBooleanArray( const TArray<bool>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendFloatArray( const TArray<float>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendIntegerArray( const TArray<int32>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendNumberArray( const TArray<double>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendStringArray( const TArray<FString>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendDateTimeArray( const TArray<FDateTime>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendGuidArray( const TArray<FGuid>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendColorArray( const TArray<FColor>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendLinearColorArray( const TArray<FLinearColor>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendRotatorArray( const TArray<FRotator>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendTransformArray( const TArray<FTransform>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendVectorArray( const TArray<FVector>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AppendObjectArray( const TArray<FJsonLibraryObject>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + AddValue( FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::Inject( int32 Index, const FJsonLibraryList& List ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + const TArray<TSharedPtr<FJsonValue>>* ListJson = List.GetJsonArray(); + if ( !ListJson ) + return; + + if ( OnNotify.IsBound() ) + { + for ( int32 i = 0; i < ListJson->Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( ( *ListJson )[ i ] ) ); + } + else + Json->Insert( *ListJson, Index ); +} + +void FJsonLibraryList::InjectBooleanArray( int32 Index, const TArray<bool>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectFloatArray( int32 Index, const TArray<float>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectIntegerArray( int32 Index, const TArray<int32>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectNumberArray( int32 Index, const TArray<double>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectStringArray( int32 Index, const TArray<FString>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectDateTimeArray( int32 Index, const TArray<FDateTime>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectGuidArray( int32 Index, const TArray<FGuid>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectColorArray( int32 Index, const TArray<FColor>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectLinearColorArray( int32 Index, const TArray<FLinearColor>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectRotatorArray( int32 Index, const TArray<FRotator>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectTransformArray( int32 Index, const TArray<FTransform>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectVectorArray( int32 Index, const TArray<FVector>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::InjectObjectArray( int32 Index, const TArray<FJsonLibraryObject>& Array ) +{ + for ( int32 i = 0; i < Array.Num(); i++ ) + InsertValue( Index + i, FJsonLibraryValue( Array[ i ] ) ); +} + +void FJsonLibraryList::AddBoolean( bool Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddFloat( float Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddInteger( int32 Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddNumber( double Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddString( const FString& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddDateTime( const FDateTime& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddGuid( const FGuid& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddColor( const FColor& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddLinearColor( const FLinearColor& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddRotator( const FRotator& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddTransform( const FTransform& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddVector( const FVector& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddValue( const FJsonLibraryValue& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + int32 Index = Json->Num(); + Json->Add( Value.JsonValue ); + NotifyAdd( Index, Value ); +} + +void FJsonLibraryList::AddObject( const FJsonLibraryObject& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddList( const FJsonLibraryList& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddArray( const TArray<FJsonLibraryValue>& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::AddMap( const TMap<FString, FJsonLibraryValue>& Value ) +{ + AddValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertBoolean( int32 Index, bool Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertFloat( int32 Index, float Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertInteger( int32 Index, int32 Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertNumber( int32 Index, double Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertString( int32 Index, const FString& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertDateTime( int32 Index, const FDateTime& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertGuid( int32 Index, const FGuid& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertColor( int32 Index, const FColor& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertLinearColor( int32 Index, const FLinearColor& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertRotator( int32 Index, const FRotator& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertTransform( int32 Index, const FTransform& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertVector( int32 Index, const FVector& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertValue( int32 Index, const FJsonLibraryValue& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + Json->Insert( Value.JsonValue, Index ); + NotifyAdd( Index, Value ); +} + +void FJsonLibraryList::InsertObject( int32 Index, const FJsonLibraryObject& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertList( int32 Index, const FJsonLibraryList& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertArray( int32 Index, const TArray<FJsonLibraryValue>& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::InsertMap( int32 Index, const TMap<FString, FJsonLibraryValue>& Value ) +{ + InsertValue( Index, FJsonLibraryValue( Value ) ); +} + +bool FJsonLibraryList::GetBoolean( int32 Index ) const +{ + return GetValue( Index ).GetBoolean(); +} + +float FJsonLibraryList::GetFloat( int32 Index ) const +{ + return GetValue( Index ).GetFloat(); +} + +int32 FJsonLibraryList::GetInteger( int32 Index ) const +{ + return GetValue( Index ).GetInteger(); +} + +double FJsonLibraryList::GetNumber( int32 Index ) const +{ + return GetValue( Index ).GetNumber(); +} + +FString FJsonLibraryList::GetString( int32 Index ) const +{ + return GetValue( Index ).GetString(); +} + +FDateTime FJsonLibraryList::GetDateTime( int32 Index ) const +{ + return GetValue( Index ).GetDateTime(); +} + +FGuid FJsonLibraryList::GetGuid( int32 Index ) const +{ + return GetValue( Index ).GetGuid(); +} + +FColor FJsonLibraryList::GetColor( int32 Index ) const +{ + return GetValue( Index ).GetColor(); +} + +FLinearColor FJsonLibraryList::GetLinearColor( int32 Index ) const +{ + return GetValue( Index ).GetLinearColor(); +} + +FRotator FJsonLibraryList::GetRotator( int32 Index ) const +{ + return GetValue( Index ).GetRotator(); +} + +FTransform FJsonLibraryList::GetTransform( int32 Index ) const +{ + return GetValue( Index ).GetTransform(); +} + +FVector FJsonLibraryList::GetVector( int32 Index ) const +{ + return GetValue( Index ).GetVector(); +} + +FJsonLibraryValue FJsonLibraryList::GetValue( int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json || Index < 0 || Index >= Json->Num() ) + return FJsonLibraryValue( TSharedPtr<FJsonValue>() ); + + return FJsonLibraryValue( ( *Json )[ Index ] ); +} + +FJsonLibraryObject FJsonLibraryList::GetObject( int32 Index ) const +{ + return GetValue( Index ).GetObject(); +} + +FJsonLibraryList FJsonLibraryList::GetList( int32 Index ) const +{ + return GetValue( Index ).GetList(); +} + +TArray<FJsonLibraryValue> FJsonLibraryList::GetArray( int32 Index ) const +{ + return GetValue( Index ).ToArray(); +} + +TMap<FString, FJsonLibraryValue> FJsonLibraryList::GetMap( int32 Index ) const +{ + return GetValue( Index ).ToMap(); +} + +void FJsonLibraryList::SetBoolean( int32 Index, bool Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetFloat( int32 Index, float Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetInteger( int32 Index, int32 Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetNumber( int32 Index, double Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetString( int32 Index, const FString& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetDateTime( int32 Index, const FDateTime& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetGuid( int32 Index, const FGuid& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetColor( int32 Index, const FColor& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetLinearColor( int32 Index, const FLinearColor& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetRotator( int32 Index, const FRotator& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetTransform( int32 Index, const FTransform& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetVector( int32 Index, const FVector& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetValue( int32 Index, const FJsonLibraryValue& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json || Index < 0 || Index >= Json->Num() ) + return; + + NotifyCheck( Index ); + ( *Json )[ Index ] = Value.JsonValue; + NotifyChange( Index, Value ); +} + +void FJsonLibraryList::SetObject( int32 Index, const FJsonLibraryObject& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetList( int32 Index, const FJsonLibraryList& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetArray( int32 Index, const TArray<FJsonLibraryValue>& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::SetMap( int32 Index, const TMap<FString, FJsonLibraryValue>& Value ) +{ + SetValue( Index, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::Remove( int32 Index ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json || Index < 0 || Index >= Json->Num() ) + return; + + NotifyCheck( Index ); + Json->RemoveAt( Index ); + NotifyRemove( Index ); +} + +void FJsonLibraryList::RemoveBoolean( bool Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( Item.IsValid() && Item->Type == EJson::Boolean && Item->AsBool() == Value ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveFloat( float Value ) +{ + RemoveNumber( Value ); +} + +void FJsonLibraryList::RemoveInteger( int32 Value ) +{ + RemoveNumber( (double)Value ); +} + +void FJsonLibraryList::RemoveNumber( double Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( Item.IsValid() && Item->Type == EJson::Number && Item->AsNumber() == Value ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveString( const FString& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( Item.IsValid() && Item->Type == EJson::String && Item->AsString() == Value ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveDateTime( const FDateTime& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( !Item.IsValid() || Item->Type != EJson::String ) + continue; + + FDateTime DateTime; + if ( !FDateTime::ParseIso8601( *Item->AsString(), DateTime ) ) + continue; + + if ( DateTime == Value ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveGuid( const FGuid& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( !Item.IsValid() || Item->Type != EJson::String ) + continue; + + FGuid Guid; + if ( !FGuid::Parse( Item->AsString(), Guid ) ) + continue; + + if ( Guid == Value ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveColor( const FColor& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsColor() && Item.GetColor() == Value ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveLinearColor( const FLinearColor& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsLinearColor() && Item.GetLinearColor() == Value ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveRotator( const FRotator& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsRotator() && Item.GetRotator().Equals( Value ) ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveTransform( const FTransform& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsTransform() && Item.GetTransform().Equals( Value ) ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveVector( const FVector& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsVector() && Item.GetVector().Equals( Value ) ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveValue( const FJsonLibraryValue& Value ) +{ + TArray<TSharedPtr<FJsonValue>>* Json = SetJsonArray(); + if ( !Json ) + return; + + for ( int32 i = Json->Num() - 1; i >= 0; i-- ) + { + if ( Value.Equals( FJsonLibraryValue( ( *Json )[ i ] ) ) ) + { + NotifyCheck( i ); + Json->RemoveAt( i ); + NotifyRemove( i ); + } + } +} + +void FJsonLibraryList::RemoveObject( const FJsonLibraryObject& Value ) +{ + RemoveValue( FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryList::RemoveList( const FJsonLibraryList& Value ) +{ + RemoveValue( FJsonLibraryValue( Value ) ); +} + +int32 FJsonLibraryList::FindBoolean( bool Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( Item.IsValid() && Item->Type == EJson::Boolean && Item->AsBool() == Value ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindFloat( float Value, int32 Index ) const +{ + return FindNumber( Value, Index ); +} + +int32 FJsonLibraryList::FindInteger( int32 Value, int32 Index ) const +{ + return FindNumber( (double)Value, Index ); +} + +int32 FJsonLibraryList::FindNumber( double Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( Item.IsValid() && Item->Type == EJson::Number && Item->AsNumber() == Value ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindString( const FString& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( Item.IsValid() && Item->Type == EJson::String && Item->AsString() == Value ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindDateTime( const FDateTime& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( !Item.IsValid() || Item->Type != EJson::String ) + continue; + + FDateTime DateTime; + if ( !FDateTime::ParseIso8601( *Item->AsString(), DateTime ) ) + continue; + + if ( DateTime == Value ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindGuid( const FGuid& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const TSharedPtr<FJsonValue>& Item = ( *Json )[ i ]; + if ( !Item.IsValid() || Item->Type != EJson::String ) + continue; + + FGuid Guid; + if ( !FGuid::Parse( Item->AsString(), Guid ) ) + continue; + + if ( Guid == Value ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindColor( const FColor& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsColor() && Item.GetColor() == Value ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindLinearColor( const FLinearColor& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsLinearColor() && Item.GetLinearColor() == Value ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindRotator( const FRotator& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsRotator() && Item.GetRotator().Equals( Value ) ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindTransform( const FTransform& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsTransform() && Item.GetTransform().Equals( Value ) ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindVector( const FVector& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + { + const FJsonLibraryValue Item = ( *Json )[ i ]; + if ( Item.IsVector() && Item.GetVector().Equals( Value ) ) + return i; + } + + return -1; +} + +int32 FJsonLibraryList::FindValue( const FJsonLibraryValue& Value, int32 Index ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return -1; + + for ( int32 i = Index; i < Json->Num(); i++ ) + if ( Value.Equals( FJsonLibraryValue( ( *Json )[ i ] ) ) ) + return i; + + return -1; +} + +int32 FJsonLibraryList::FindObject( const FJsonLibraryObject& Value, int32 Index ) const +{ + return FindValue( FJsonLibraryValue( Value ), Index ); +} + +int32 FJsonLibraryList::FindList( const FJsonLibraryList& Value, int32 Index ) const +{ + return FindValue( FJsonLibraryValue( Value ), Index ); +} + +const TArray<TSharedPtr<FJsonValue>>* FJsonLibraryList::GetJsonArray() const +{ + if ( JsonArray.IsValid() && JsonArray->Type == EJson::Array ) + { + const TArray<TSharedPtr<FJsonValue>>* Array; + if ( JsonArray->TryGetArray( Array ) ) + return Array; + } + + return nullptr; +} + +TArray<TSharedPtr<FJsonValue>>* FJsonLibraryList::SetJsonArray() +{ + return const_cast<TArray<TSharedPtr<FJsonValue>>*>( GetJsonArray() ); +} + +bool FJsonLibraryList::TryParse( const FString& Text, bool bStripComments /*= false*/, bool bStripTrailingCommas /*= false*/ ) +{ + if ( Text.IsEmpty() ) + return false; + + FString TrimmedText = Text; + TrimmedText.TrimStartInline(); + TrimmedText.TrimEndInline(); + + if ( bStripComments || bStripTrailingCommas ) + TrimmedText = UJsonLibraryHelpers::StripCommentsOrCommas( TrimmedText, bStripComments, bStripTrailingCommas ); + + if ( !TrimmedText.StartsWith( "[" ) || !TrimmedText.EndsWith( "]" ) ) + return false; + + TArray<TSharedPtr<FJsonValue>> Array; + TSharedRef<TJsonReader<TCHAR>> Reader = TJsonReaderFactory<TCHAR>::Create( TrimmedText ); + if ( !FJsonSerializer::Deserialize( Reader, Array ) ) + return false; + + JsonArray = MakeShareable( new FJsonValueArray( Array ) ); + + NotifyParse(); + return true; +} + +bool FJsonLibraryList::TryStringify( FString& Text, bool bCondensed /*= true*/ ) const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return false; + + if ( Json->Num() <= 0 ) + { + if ( bCondensed ) + Text = FString( "[]" ); + else + Text = FString::Printf( TEXT( "[%s]" ), LINE_TERMINATOR ); + + return true; + } + + if ( bCondensed ) + { + TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( *Json, Writer ) ) + return false; + } + else + { + TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( *Json, Writer ) ) + return false; + } + + Text.TrimStartInline(); + Text.TrimEndInline(); + + if ( !Text.StartsWith( "[" ) || !Text.EndsWith( "]" ) ) + return false; + + return true; +} + +void FJsonLibraryList::NotifyAdd( int32 Index, const FJsonLibraryValue& Value ) +{ + if ( !OnNotify.IsBound() ) + return; + + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Added, Index, Value ); + + bNotifyHasIndex = false; + NotifyValue.Reset(); +} + +void FJsonLibraryList::NotifyChange( int32 Index, const FJsonLibraryValue& Value ) +{ + if ( !OnNotify.IsBound() ) + return; + + if ( !Value.Equals( FJsonLibraryValue( NotifyValue ), true ) ) + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Changed, Index, Value ); + else + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::None, Index, Value ); + + bNotifyHasIndex = false; + NotifyValue.Reset(); +} + +bool FJsonLibraryList::NotifyCheck() +{ + if ( !OnNotify.IsBound() ) + return false; + + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( Json ) + bNotifyHasIndex = Json->Num() > 0; + else + bNotifyHasIndex = false; + + NotifyValue.Reset(); + return bNotifyHasIndex; +} + +bool FJsonLibraryList::NotifyCheck( int32 Index ) +{ + if ( !OnNotify.IsBound() ) + return false; + + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( Json ) + bNotifyHasIndex = Index >= 0 && Index < Json->Num(); + else + bNotifyHasIndex = false; + + NotifyValue.Reset(); + if ( bNotifyHasIndex ) + NotifyValue = ( *Json )[ Index ]; + + return bNotifyHasIndex; +} + +void FJsonLibraryList::NotifyClear() +{ + if ( !OnNotify.IsBound() ) + return; + + NotifyValue.Reset(); + if ( bNotifyHasIndex ) + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Reset, -1, FJsonLibraryValue( TSharedPtr<FJsonValue>() ) ); + else + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::None, -1, FJsonLibraryValue( TSharedPtr<FJsonValue>() ) ); + + bNotifyHasIndex = false; +} + +void FJsonLibraryList::NotifyParse() +{ + if ( !OnNotify.IsBound() ) + return; + + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return; + + NotifyValue.Reset(); + for ( int32 i = 0; i < Json->Num(); i++ ) + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Added, i, FJsonLibraryValue( ( *Json )[ i ] ) ); + + bNotifyHasIndex = false; +} + +void FJsonLibraryList::NotifyRemove( int32 Index ) +{ + if ( !OnNotify.IsBound() ) + return; + + if ( bNotifyHasIndex ) + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Removed, Index, FJsonLibraryValue( NotifyValue ) ); + else + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::None, Index, FJsonLibraryValue( TSharedPtr<FJsonValue>() ) ); + + bNotifyHasIndex = false; + NotifyValue.Reset(); +} + +bool FJsonLibraryList::IsValid() const +{ + if ( GetJsonArray() ) + return true; + + return false; +} + +bool FJsonLibraryList::IsEmpty() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + if ( !Json ) + return false; + + return Json->Num() == 0; +} + +FJsonLibraryList FJsonLibraryList::Parse( const FString& Text ) +{ + FJsonLibraryList List = TSharedPtr<FJsonValueArray>(); + if ( !List.TryParse( Text ) ) + List.JsonArray.Reset(); + + return List; +} + +FJsonLibraryList FJsonLibraryList::Parse( const FString& Text, const FJsonLibraryListNotify& Notify ) +{ + FJsonLibraryList List = Parse( Text ); + List.OnNotify = Notify; + + return List; +} + +FJsonLibraryList FJsonLibraryList::ParseRelaxed( const FString& Text, bool bStripComments /*= true*/, bool bStripTrailingCommas /*= true*/ ) +{ + FJsonLibraryList List = TSharedPtr<FJsonValueArray>(); + if ( !List.TryParse( Text, bStripComments, bStripTrailingCommas ) ) + List.JsonArray.Reset(); + + return List; +} + +FString FJsonLibraryList::Stringify( bool bCondensed /*= true*/ ) const +{ + FString Text; + if ( TryStringify( Text, bCondensed ) ) + return Text; + + return FString(); +} + +TArray<FJsonLibraryValue> FJsonLibraryList::ToArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FJsonLibraryValue> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ) ); + + return Array; +} + +TArray<bool> FJsonLibraryList::ToBooleanArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<bool> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetBoolean() ); + + return Array; +} + +TArray<float> FJsonLibraryList::ToFloatArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<float> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetFloat() ); + + return Array; +} + +TArray<int32> FJsonLibraryList::ToIntegerArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<int32> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetInteger() ); + + return Array; +} + +TArray<double> FJsonLibraryList::ToNumberArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<double> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetNumber() ); + + return Array; +} + +TArray<FString> FJsonLibraryList::ToStringArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FString> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetString() ); + + return Array; +} + +TArray<FDateTime> FJsonLibraryList::ToDateTimeArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FDateTime> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetDateTime() ); + + return Array; +} + +TArray<FGuid> FJsonLibraryList::ToGuidArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FGuid> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetGuid() ); + + return Array; +} + +TArray<FColor> FJsonLibraryList::ToColorArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FColor> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetColor() ); + + return Array; +} + +TArray<FLinearColor> FJsonLibraryList::ToLinearColorArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FLinearColor> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetLinearColor() ); + + return Array; +} + +TArray<FRotator> FJsonLibraryList::ToRotatorArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FRotator> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetRotator() ); + + return Array; +} + +TArray<FTransform> FJsonLibraryList::ToTransformArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FTransform> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetTransform() ); + + return Array; +} + +TArray<FVector> FJsonLibraryList::ToVectorArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FVector> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryValue( ( *Json )[ i ] ).GetVector() ); + + return Array; +} + +TArray<FJsonLibraryObject> FJsonLibraryList::ToObjectArray() const +{ + const TArray<TSharedPtr<FJsonValue>>* Json = GetJsonArray(); + + TArray<FJsonLibraryObject> Array; + if ( !Json ) + return Array; + + for ( int32 i = 0; i < Json->Num(); i++ ) + Array.Add( FJsonLibraryObject( ( *Json )[ i ] ) ); + + return Array; +} + +bool FJsonLibraryList::operator==( const FJsonLibraryList& List ) const +{ + return Equals( List ); +} + +bool FJsonLibraryList::operator!=( const FJsonLibraryList& List ) const +{ + return !Equals( List ); +} + +bool FJsonLibraryList::operator==( const FJsonLibraryValue& Value ) const +{ + return Value.Equals( FJsonLibraryValue( *this ) ); +} + +bool FJsonLibraryList::operator!=( const FJsonLibraryValue& Value ) const +{ + return !Value.Equals( FJsonLibraryValue( *this ) ); +} diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryModule.cpp b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7509d38772fa9c39effae335c80c2c222a4cc038 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryModule.cpp @@ -0,0 +1,19 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "JsonLibraryModule.h" +#include "Modules/ModuleManager.h" + +class FJsonLibraryModule : public IJsonLibraryModule +{ +public: + virtual void StartupModule() override + { + // + } + + virtual void ShutdownModule() override + { + // + } +}; + +IMPLEMENT_MODULE(FJsonLibraryModule, JsonLibrary); diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryObject.cpp b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryObject.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c274c2bc0491d40643d8a5ff8aa599d1309511d0 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryObject.cpp @@ -0,0 +1,1186 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "JsonLibraryObject.h" +#include "JsonLibraryConverter.h" +#include "JsonLibraryList.h" +#include "JsonLibraryHelpers.h" +#include "Policies/CondensedJsonPrintPolicy.h" +#include "Policies/PrettyJsonPrintPolicy.h" + +FJsonLibraryObject::FJsonLibraryObject( const TSharedPtr<FJsonValue>& Value ) +{ + if ( Value.IsValid() && Value->Type == EJson::Object ) + JsonObject = StaticCastSharedPtr<FJsonValueObject>( Value ); +} + +FJsonLibraryObject::FJsonLibraryObject( const TSharedPtr<FJsonValueObject>& Value ) +{ + JsonObject = Value; +} + +FJsonLibraryObject::FJsonLibraryObject( const UStruct* StructType, const void* StructPtr ) + : FJsonLibraryObject() +{ + if ( StructType && StructPtr ) + { + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + if ( !FJsonLibraryConverter::UStructToJsonObject( StructType, StructPtr, Json.ToSharedRef() ) ) + JsonObject.Reset(); + } + } + else + JsonObject.Reset(); +} + +FJsonLibraryObject::FJsonLibraryObject( const TSharedPtr<FStructOnScope>& StructData ) + : FJsonLibraryObject() +{ + if ( StructData.IsValid() ) + { + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + if ( !FJsonLibraryConverter::UStructToJsonObject( StructData->GetStruct(), StructData->GetStructMemory(), Json.ToSharedRef() ) ) + JsonObject.Reset(); + } + } + else + JsonObject.Reset(); +} + +FJsonLibraryObject::FJsonLibraryObject() +{ + JsonObject = MakeShareable( new FJsonValueObject( MakeShareable( new FJsonObject() ) ) ); +} + +FJsonLibraryObject::FJsonLibraryObject( const FJsonLibraryObjectNotify& Notify ) + : FJsonLibraryObject() +{ + OnNotify = Notify; +} + +FJsonLibraryObject::FJsonLibraryObject( const FLinearColor& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + Json->SetNumberField( "r", Value.R ); + Json->SetNumberField( "g", Value.G ); + Json->SetNumberField( "b", Value.B ); + if ( Value.A != 1.0f ) + Json->SetNumberField( "a", Value.A ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const FRotator& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + Json->SetNumberField( "pitch", Value.Pitch ); + Json->SetNumberField( "yaw", Value.Yaw ); + Json->SetNumberField( "roll", Value.Roll ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const FTransform& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + Json->SetField( "rotation", FJsonLibraryObject( Value.GetRotation().Rotator() ).JsonObject ); + Json->SetField( "translation", FJsonLibraryObject( Value.GetTranslation() ).JsonObject ); + + const FVector Scale = Value.GetScale3D(); + if ( Scale != FVector::OneVector ) + { + if ( Scale.IsUniform() ) + Json->SetNumberField( "scale", Scale.X ); + else + Json->SetField( "scale", FJsonLibraryObject( Scale ).JsonObject ); + } + } +} + +FJsonLibraryObject::FJsonLibraryObject( const FVector& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + Json->SetNumberField( "x", Value.X ); + Json->SetNumberField( "y", Value.Y ); + Json->SetNumberField( "z", Value.Z ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FJsonLibraryValue>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FJsonLibraryValue>& Temp : Value ) + Json->SetField( Temp.Key, Temp.Value.JsonValue ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, bool>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, bool>& Temp : Value ) + Json->SetBoolField( Temp.Key, Temp.Value ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, float>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, float>& Temp : Value ) + Json->SetNumberField( Temp.Key, Temp.Value ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, double>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, double>& Temp : Value ) + Json->SetNumberField( Temp.Key, Temp.Value ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, int32>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, int32>& Temp : Value ) + Json->SetNumberField( Temp.Key, (double)Temp.Value ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FString>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FString>& Temp : Value ) + Json->SetStringField( Temp.Key, Temp.Value ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FDateTime>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FDateTime>& Temp : Value ) + Json->SetStringField( Temp.Key, Temp.Value.ToIso8601() ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FGuid>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FGuid>& Temp : Value ) + Json->SetStringField( Temp.Key, Temp.Value.ToString( EGuidFormats::DigitsWithHyphens ) ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FColor>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FColor>& Temp : Value ) + Json->SetStringField( Temp.Key, "#" + Temp.Value.ToHex() ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FLinearColor>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FLinearColor>& Temp : Value ) + Json->SetField( Temp.Key, FJsonLibraryObject( Temp.Value ).JsonObject ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FRotator>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FRotator>& Temp : Value ) + Json->SetField( Temp.Key, FJsonLibraryObject( Temp.Value ).JsonObject ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FTransform>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FTransform>& Temp : Value ) + Json->SetField( Temp.Key, FJsonLibraryObject( Temp.Value ).JsonObject ); + } +} + +FJsonLibraryObject::FJsonLibraryObject( const TMap<FString, FVector>& Value ) + : FJsonLibraryObject() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( Json.IsValid() ) + { + for ( const TPair<FString, FVector>& Temp : Value ) + Json->SetField( Temp.Key, FJsonLibraryObject( Temp.Value ).JsonObject ); + } +} + +bool FJsonLibraryObject::Equals( const FJsonLibraryObject& Object ) const +{ + if ( !JsonObject.IsValid() || !Object.JsonObject.IsValid() ) + return false; + + if ( JsonObject == Object.JsonObject ) + return true; + + const TSharedPtr<FJsonObject> JsonA = GetJsonObject(); + const TSharedPtr<FJsonObject> JsonB = Object.GetJsonObject(); + if ( JsonA.IsValid() && JsonB.IsValid() ) + return JsonA == JsonB; + + return false; +} + +int32 FJsonLibraryObject::Count() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( !Json.IsValid() ) + return 0; + + return Json->Values.Num(); +} + +void FJsonLibraryObject::Clear() +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( !Json.IsValid() ) + return; + + NotifyCheck(); + Json->Values.Empty(); + NotifyClear(); +} + +bool FJsonLibraryObject::HasKey( const FString& Key ) const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( !Json.IsValid() ) + return false; + + return Json->HasField( Key ); +} + +void FJsonLibraryObject::RemoveKey( const FString& Key ) +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( !Json.IsValid() ) + return; + + NotifyCheck( Key ); + Json->RemoveField( Key ); + NotifyRemove( Key ); +} + +void FJsonLibraryObject::Add( const FJsonLibraryObject& Object ) +{ + const TSharedPtr<FJsonObject> ObjectJson = Object.GetJsonObject(); + if ( !ObjectJson.IsValid() ) + return; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : ObjectJson->Values ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddBooleanMap( const TMap<FString, bool>& Map ) +{ + for ( const TPair<FString, bool>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddFloatMap( const TMap<FString, float>& Map ) +{ + for ( const TPair<FString, float>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddIntegerMap( const TMap<FString, int32>& Map ) +{ + for ( const TPair<FString, int32>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddNumberMap( const TMap<FString, double>& Map ) +{ + for ( const TPair<FString, double>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddStringMap( const TMap<FString, FString>& Map ) +{ + for ( const TPair<FString, FString>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddDateTimeMap( const TMap<FString, FDateTime>& Map ) +{ + for ( const TPair<FString, FDateTime>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddGuidMap( const TMap<FString, FGuid>& Map ) +{ + for ( const TPair<FString, FGuid>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddColorMap( const TMap<FString, FColor>& Map ) +{ + for ( const TPair<FString, FColor>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddLinearColorMap( const TMap<FString, FLinearColor>& Map ) +{ + for ( const TPair<FString, FLinearColor>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddRotatorMap( const TMap<FString, FRotator>& Map ) +{ + for ( const TPair<FString, FRotator>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddTransformMap( const TMap<FString, FTransform>& Map ) +{ + for ( const TPair<FString, FTransform>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +void FJsonLibraryObject::AddVectorMap( const TMap<FString, FVector>& Map ) +{ + for ( const TPair<FString, FVector>& Temp : Map ) + SetValue( Temp.Key, FJsonLibraryValue( Temp.Value ) ); +} + +TArray<FString> FJsonLibraryObject::GetKeys() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TArray<FString> Keys; + if ( !Json.IsValid() ) + return Keys; + + Json->Values.GetKeys( Keys ); + return Keys; +} + +TArray<FJsonLibraryValue> FJsonLibraryObject::GetValues() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TArray<FJsonLibraryValue> Values; + if ( !Json.IsValid() ) + return Values; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Values.Add( FJsonLibraryValue( Temp.Value ) ); + + return Values; +} + +bool FJsonLibraryObject::GetBoolean( const FString& Key ) const +{ + return GetValue( Key ).GetBoolean(); +} + +float FJsonLibraryObject::GetFloat( const FString& Key ) const +{ + return GetValue( Key ).GetFloat(); +} + +int32 FJsonLibraryObject::GetInteger( const FString& Key ) const +{ + return GetValue( Key ).GetInteger(); +} + +double FJsonLibraryObject::GetNumber( const FString& Key ) const +{ + return GetValue( Key ).GetNumber(); +} + +FString FJsonLibraryObject::GetString( const FString& Key ) const +{ + return GetValue( Key ).GetString(); +} + +FDateTime FJsonLibraryObject::GetDateTime( const FString& Key ) const +{ + return GetValue( Key ).GetDateTime(); +} + +FGuid FJsonLibraryObject::GetGuid( const FString& Key ) const +{ + return GetValue( Key ).GetGuid(); +} + +FColor FJsonLibraryObject::GetColor( const FString& Key ) const +{ + return GetValue( Key ).GetColor(); +} + +FLinearColor FJsonLibraryObject::GetLinearColor( const FString& Key ) const +{ + return GetValue( Key ).GetLinearColor(); +} + +FRotator FJsonLibraryObject::GetRotator( const FString& Key ) const +{ + return GetValue( Key ).GetRotator(); +} + +FTransform FJsonLibraryObject::GetTransform( const FString& Key ) const +{ + return GetValue( Key ).GetTransform(); +} + +FVector FJsonLibraryObject::GetVector( const FString& Key ) const +{ + return GetValue( Key ).GetVector(); +} + +FJsonLibraryValue FJsonLibraryObject::GetValue( const FString& Key ) const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( !Json.IsValid() ) + return FJsonLibraryValue( TSharedPtr<FJsonValue>() ); + + return FJsonLibraryValue( Json->TryGetField( Key ) ); +} + +FJsonLibraryObject FJsonLibraryObject::GetObject( const FString& Key ) const +{ + return GetValue( Key ).GetObject(); +} + +FJsonLibraryList FJsonLibraryObject::GetList( const FString& Key ) const +{ + return GetValue( Key ).GetList(); +} + +TArray<FJsonLibraryValue> FJsonLibraryObject::GetArray( const FString& Key ) const +{ + return GetValue( Key ).ToArray(); +} + +TMap<FString, FJsonLibraryValue> FJsonLibraryObject::GetMap( const FString& Key ) const +{ + return GetValue( Key ).ToMap(); +} + +void FJsonLibraryObject::SetBoolean( const FString& Key, bool Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetFloat( const FString& Key, float Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetInteger( const FString& Key, int32 Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetNumber( const FString& Key, double Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetString( const FString& Key, const FString& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetDateTime( const FString& Key, const FDateTime& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetGuid( const FString& Key, const FGuid& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetColor( const FString& Key, const FColor& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetLinearColor( const FString& Key, const FLinearColor& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetRotator( const FString& Key, const FRotator& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetTransform( const FString& Key, const FTransform& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetVector( const FString& Key, const FVector& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetValue( const FString& Key, const FJsonLibraryValue& Value ) +{ + TSharedPtr<FJsonObject> Json = SetJsonObject(); + if ( !Json.IsValid() ) + return; + + NotifyCheck( Key ); + Json->SetField( Key, Value.JsonValue ); + NotifyAddOrChange( Key, Value ); +} + +void FJsonLibraryObject::SetObject( const FString& Key, const FJsonLibraryObject& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetList( const FString& Key, const FJsonLibraryList& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetArray( const FString& Key, const TArray<FJsonLibraryValue>& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +void FJsonLibraryObject::SetMap( const FString& Key, const TMap<FString, FJsonLibraryValue>& Value ) +{ + SetValue( Key, FJsonLibraryValue( Value ) ); +} + +const TSharedPtr<FJsonObject> FJsonLibraryObject::GetJsonObject() const +{ + if ( JsonObject.IsValid() && JsonObject->Type == EJson::Object ) + { + const TSharedPtr<FJsonObject>* Object; + if ( JsonObject->TryGetObject( Object ) && Object ) + return *Object; + } + + return TSharedPtr<FJsonObject>(); +} + +TSharedPtr<FJsonObject> FJsonLibraryObject::SetJsonObject() +{ + return GetJsonObject(); +} + +bool FJsonLibraryObject::TryParse( const FString& Text, bool bStripComments /*= false*/, bool bStripTrailingCommas /*= false*/ ) +{ + if ( Text.IsEmpty() ) + return false; + + FString TrimmedText = Text; + TrimmedText.TrimStartInline(); + TrimmedText.TrimEndInline(); + + if ( bStripComments || bStripTrailingCommas ) + TrimmedText = UJsonLibraryHelpers::StripCommentsOrCommas( TrimmedText, bStripComments, bStripTrailingCommas ); + + if ( !TrimmedText.StartsWith( "{" ) || !TrimmedText.EndsWith( "}" ) ) + return false; + + TSharedPtr<FJsonObject> Object; + TSharedRef<TJsonReader<TCHAR>> Reader = TJsonReaderFactory<TCHAR>::Create( TrimmedText ); + if ( !FJsonSerializer::Deserialize( Reader, Object ) || !Object.IsValid() ) + return false; + + JsonObject = MakeShareable( new FJsonValueObject( Object ) ); + + NotifyParse(); + return true; +} + +bool FJsonLibraryObject::TryStringify( FString& Text, bool bCondensed /*= true*/ ) const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( !Json.IsValid() ) + return false; + + if ( bCondensed ) + { + TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( Json.ToSharedRef(), Writer ) ) + return false; + } + else + { + TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( Json.ToSharedRef(), Writer ) ) + return false; + } + + Text.TrimStartInline(); + Text.TrimEndInline(); + + if ( !Text.StartsWith( "{" ) || !Text.EndsWith( "}" ) ) + return false; + + return true; +} + +void FJsonLibraryObject::NotifyAddOrChange( const FString& Key, const FJsonLibraryValue& Value ) +{ + if ( !OnNotify.IsBound() ) + return; + + if ( bNotifyHasKey ) + { + if ( !Value.Equals( FJsonLibraryValue( NotifyValue ), true ) ) + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Changed, Key, Value ); + else + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::None, Key, Value ); + } + else + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Added, Key, Value ); + + bNotifyHasKey = false; + NotifyValue.Reset(); +} + +bool FJsonLibraryObject::NotifyCheck() +{ + if ( !OnNotify.IsBound() ) + return false; + + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( Json.IsValid() ) + bNotifyHasKey = Json->Values.Num() > 0; + else + bNotifyHasKey = false; + + NotifyValue.Reset(); + return bNotifyHasKey; +} + +bool FJsonLibraryObject::NotifyCheck( const FString& Key ) +{ + if ( !OnNotify.IsBound() ) + return false; + + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( Json.IsValid() ) + bNotifyHasKey = Json->HasField( Key ); + else + bNotifyHasKey = false; + + NotifyValue.Reset(); + if ( bNotifyHasKey ) + NotifyValue = Json->TryGetField( Key ); + + return bNotifyHasKey; +} + +void FJsonLibraryObject::NotifyClear() +{ + if ( !OnNotify.IsBound() ) + return; + + NotifyValue.Reset(); + if ( bNotifyHasKey ) + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Reset, FString(), FJsonLibraryValue( TSharedPtr<FJsonValue>() ) ); + else + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::None, FString(), FJsonLibraryValue( TSharedPtr<FJsonValue>() ) ); + + bNotifyHasKey = false; +} + +void FJsonLibraryObject::NotifyParse() +{ + if ( !OnNotify.IsBound() ) + return; + + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( !Json.IsValid() ) + return; + + NotifyValue.Reset(); + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Added, Temp.Key, FJsonLibraryValue( Temp.Value ) ); + + bNotifyHasKey = false; +} + +void FJsonLibraryObject::NotifyRemove( const FString& Key ) +{ + if ( !OnNotify.IsBound() ) + return; + + if ( bNotifyHasKey ) + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::Removed, Key, FJsonLibraryValue( NotifyValue ) ); + else + OnNotify.Execute( FJsonLibraryValue( *this ), EJsonLibraryNotifyAction::None, Key, FJsonLibraryValue( TSharedPtr<FJsonValue>() ) ); + + bNotifyHasKey = false; + NotifyValue.Reset(); +} + +bool FJsonLibraryObject::IsValid() const +{ + return GetJsonObject().IsValid(); +} + +bool FJsonLibraryObject::IsEmpty() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( !Json.IsValid() ) + return false; + + return Json->Values.Num() == 0; +} + +bool FJsonLibraryObject::IsLinearColor() const +{ + int32 Keys = Count(); + if ( Keys < 3 || Keys > 4 ) + return false; + + if ( !HasKey( "r" ) + || !HasKey( "g" ) + || !HasKey( "b" ) ) + return false; + + if ( Keys == 3 ) + return true; + if ( Keys == 4 && HasKey( "a" ) ) + return true; + + return false; +} + +bool FJsonLibraryObject::IsRotator() const +{ + if ( Count() != 3 ) + return false; + + if ( HasKey( "pitch" ) + && HasKey( "yaw" ) + && HasKey( "roll" ) ) + return true; + + return false; +} + +bool FJsonLibraryObject::IsTransform() const +{ + int32 Keys = Count(); + if ( Keys == 0 || Keys > 3 ) + return false; + + if ( !HasKey( "rotation" ) + || !HasKey( "translation" ) ) + return false; + + if ( !GetValue( "rotation" ).IsRotator() ) + return false; + if ( !GetValue( "translation" ).IsVector() ) + return false; + + if ( Keys == 2 ) + return true; + + if ( Keys == 3 && HasKey( "scale" ) ) + { + FJsonLibraryValue Scale = GetValue( "scale" ); + switch ( Scale.GetType() ) + { + case EJsonLibraryType::Number: + case EJsonLibraryType::String: + return true; + + case EJsonLibraryType::Object: + if ( Scale.IsVector() ) + return true; + } + } + + return false; +} + +bool FJsonLibraryObject::IsVector() const +{ + if ( Count() != 3 ) + return false; + + if ( HasKey( "x" ) + && HasKey( "y" ) + && HasKey( "z" ) ) + return true; + + return false; +} + +FJsonLibraryObject FJsonLibraryObject::Parse( const FString& Text ) +{ + FJsonLibraryObject Object = TSharedPtr<FJsonValueObject>(); + if ( !Object.TryParse( Text ) ) + Object.JsonObject.Reset(); + + return Object; +} + +FJsonLibraryObject FJsonLibraryObject::Parse( const FString& Text, const FJsonLibraryObjectNotify& Notify ) +{ + FJsonLibraryObject Object = Parse( Text ); + Object.OnNotify = Notify; + + return Object; +} + +FJsonLibraryObject FJsonLibraryObject::ParseRelaxed( const FString& Text, bool bStripComments /*= true*/, bool bStripTrailingCommas /*= true*/ ) +{ + FJsonLibraryObject Object = TSharedPtr<FJsonValueObject>(); + if ( !Object.TryParse( Text, bStripComments, bStripTrailingCommas ) ) + Object.JsonObject.Reset(); + + return Object; +} + +FString FJsonLibraryObject::Stringify( bool bCondensed /*= true*/ ) const +{ + FString Text; + if ( TryStringify( Text, bCondensed ) ) + return Text; + + return FString(); +} + + +bool FJsonLibraryObject::ToStruct( const UStruct* StructType, void* StructPtr ) const +{ + if ( !StructType || !StructPtr ) + return false; + + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + if ( !Json.IsValid() ) + return false; + + return FJsonLibraryConverter::JsonObjectToUStruct( Json.ToSharedRef(), StructType, StructPtr ); +} + +TSharedPtr<FStructOnScope> FJsonLibraryObject::ToStruct( const UStruct* StructType ) const +{ + if ( StructType ) + { + TSharedPtr<FStructOnScope> StructData = MakeShareable( new FStructOnScope( StructType ) ); + if ( ToStruct( StructType, StructData->GetStructMemory() ) ) + return StructData; + } + + return TSharedPtr<FStructOnScope>(); +} + +FLinearColor FJsonLibraryObject::ToLinearColor() const +{ + if ( !IsLinearColor() ) + return FLinearColor(); + + return FLinearColor( GetFloat( "r" ), + GetFloat( "g" ), + GetFloat( "b" ), + HasKey( "a" ) ? GetFloat( "a" ) : 1.0f ); +} + +FRotator FJsonLibraryObject::ToRotator() const +{ + if ( !IsRotator() ) + return FRotator::ZeroRotator; + + return FRotator( GetFloat( "pitch" ), + GetFloat( "yaw" ), + GetFloat( "roll" ) ); +} + +FTransform FJsonLibraryObject::ToTransform() const +{ + if ( !IsTransform() ) + return FTransform::Identity; + + const FRotator Rotation = GetRotator( "rotation" ); + const FVector Translation = GetVector( "translation" ); + if ( !HasKey( "scale" ) ) + return FTransform( Rotation, Translation ); + + FJsonLibraryValue Scale = GetValue( "scale" ); + switch ( Scale.GetType() ) + { + case EJsonLibraryType::Number: + case EJsonLibraryType::String: + return FTransform( Rotation, Translation, FVector( Scale.GetNumber() ) ); + + case EJsonLibraryType::Object: + if ( Scale.IsVector() ) + return FTransform( Rotation, Translation, Scale.GetVector() ); + } + + return FTransform( Rotation, Translation ); +} + +FVector FJsonLibraryObject::ToVector() const +{ + if ( !IsVector() ) + return FVector::ZeroVector; + + return FVector( GetFloat( "x" ), + GetFloat( "y" ), + GetFloat( "z" ) ); +} + +TMap<FString, FJsonLibraryValue> FJsonLibraryObject::ToMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FJsonLibraryValue> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ) ); + + return Map; +} + +TMap<FString, bool> FJsonLibraryObject::ToBooleanMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, bool> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetBoolean() ); + + return Map; +} + +TMap<FString, float> FJsonLibraryObject::ToFloatMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, float> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetFloat() ); + + return Map; +} + +TMap<FString, int32> FJsonLibraryObject::ToIntegerMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, int32> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetInteger() ); + + return Map; +} + +TMap<FString, double> FJsonLibraryObject::ToNumberMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, double> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetNumber() ); + + return Map; +} + +TMap<FString, FString> FJsonLibraryObject::ToStringMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FString> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetString() ); + + return Map; +} + +TMap<FString, FDateTime> FJsonLibraryObject::ToDateTimeMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FDateTime> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetDateTime() ); + + return Map; +} + +TMap<FString, FGuid> FJsonLibraryObject::ToGuidMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FGuid> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetGuid() ); + + return Map; +} + +TMap<FString, FColor> FJsonLibraryObject::ToColorMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FColor> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetColor() ); + + return Map; +} + +TMap<FString, FLinearColor> FJsonLibraryObject::ToLinearColorMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FLinearColor> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetLinearColor() ); + + return Map; +} + +TMap<FString, FRotator> FJsonLibraryObject::ToRotatorMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FRotator> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetRotator() ); + + return Map; +} + +TMap<FString, FTransform> FJsonLibraryObject::ToTransformMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FTransform> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetTransform() ); + + return Map; +} + +TMap<FString, FVector> FJsonLibraryObject::ToVectorMap() const +{ + const TSharedPtr<FJsonObject> Json = GetJsonObject(); + + TMap<FString, FVector> Map; + if ( !Json.IsValid() ) + return Map; + + for ( const TPair<FString, TSharedPtr<FJsonValue>>& Temp : Json->Values ) + Map.Add( Temp.Key, FJsonLibraryValue( Temp.Value ).GetVector() ); + + return Map; +} + +bool FJsonLibraryObject::operator==( const FJsonLibraryObject& Object ) const +{ + return Equals( Object ); +} + +bool FJsonLibraryObject::operator!=( const FJsonLibraryObject& Object ) const +{ + return !Equals( Object ); +} + +bool FJsonLibraryObject::operator==( const FJsonLibraryValue& Value ) const +{ + return Value.Equals( FJsonLibraryValue( *this ) ); +} + +bool FJsonLibraryObject::operator!=( const FJsonLibraryValue& Value ) const +{ + return !Value.Equals( FJsonLibraryValue( *this ) ); +} diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryValue.cpp b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryValue.cpp new file mode 100644 index 0000000000000000000000000000000000000000..47cf928a1326f543e5d92d4637e5671fce647379 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Private/JsonLibraryValue.cpp @@ -0,0 +1,737 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "JsonLibraryValue.h" +#include "JsonLibraryObject.h" +#include "JsonLibraryList.h" +#include "JsonLibraryHelpers.h" +#include "Policies/CondensedJsonPrintPolicy.h" +#include "Policies/PrettyJsonPrintPolicy.h" + +FJsonLibraryValue::FJsonLibraryValue( const TSharedPtr<FJsonValue>& Value ) +{ + JsonValue = Value; +} + +FJsonLibraryValue::FJsonLibraryValue() +{ + JsonValue = MakeShareable( new FJsonValueNull() ); +} + +FJsonLibraryValue::FJsonLibraryValue( bool Value ) +{ + JsonValue = MakeShareable( new FJsonValueBoolean( Value ) ); +} + +FJsonLibraryValue::FJsonLibraryValue( float Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( double Value ) +{ + JsonValue = MakeShareable( new FJsonValueNumber( Value ) ); +} + +FJsonLibraryValue::FJsonLibraryValue( int8 Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( uint8 Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( int16 Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( uint16 Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( int32 Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( uint32 Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( int64 Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( uint64 Value ) + : FJsonLibraryValue( (double)Value ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( const FString& Value ) +{ + JsonValue = MakeShareable( new FJsonValueString( Value ) ); +} + +FJsonLibraryValue::FJsonLibraryValue( const FDateTime& Value ) + : FJsonLibraryValue( Value.ToIso8601() ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( const FGuid& Value ) + : FJsonLibraryValue( Value.ToString( EGuidFormats::DigitsWithHyphens ) ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( const FColor& Value ) + : FJsonLibraryValue( "#" + Value.ToHex() ) +{ + // +} + +FJsonLibraryValue::FJsonLibraryValue( const FLinearColor& Value ) +{ + JsonValue = FJsonLibraryObject( Value ).JsonObject; +} + +FJsonLibraryValue::FJsonLibraryValue( const FRotator& Value ) +{ + JsonValue = FJsonLibraryObject( Value ).JsonObject; +} + +FJsonLibraryValue::FJsonLibraryValue( const FTransform& Value ) +{ + JsonValue = FJsonLibraryObject( Value ).JsonObject; +} + +FJsonLibraryValue::FJsonLibraryValue( const FVector& Value ) +{ + JsonValue = FJsonLibraryObject( Value ).JsonObject; +} + +FJsonLibraryValue::FJsonLibraryValue( const FJsonLibraryObject& Value ) +{ + JsonValue = Value.JsonObject; +} + +FJsonLibraryValue::FJsonLibraryValue( const FJsonLibraryList& Value ) +{ + JsonValue = Value.JsonArray; +} + +FJsonLibraryValue::FJsonLibraryValue( const TArray<FJsonLibraryValue>& Value ) +{ + JsonValue = FJsonLibraryList( Value ).JsonArray; +} + +FJsonLibraryValue::FJsonLibraryValue( const TMap<FString, FJsonLibraryValue>& Value ) +{ + JsonValue = FJsonLibraryObject( Value ).JsonObject; +} + +EJsonLibraryType FJsonLibraryValue::GetType() const +{ + if ( !JsonValue.IsValid() ) + return EJsonLibraryType::Invalid; + + switch ( JsonValue->Type ) + { + case EJson::Null: return EJsonLibraryType::Null; + case EJson::Boolean: return EJsonLibraryType::Boolean; + case EJson::Number: return EJsonLibraryType::Number; + case EJson::String: return EJsonLibraryType::String; + case EJson::Object: return EJsonLibraryType::Object; + case EJson::Array: return EJsonLibraryType::Array; + } + + return EJsonLibraryType::Invalid; +} + +bool FJsonLibraryValue::Equals( const FJsonLibraryValue& Value, bool bStrict /*= false*/ ) const +{ + if ( !JsonValue.IsValid() ) + { + if ( !Value.JsonValue.IsValid() ) + return true; + + EJson Type = Value.JsonValue->Type; + if ( Type == EJson::None ) + return true; + if ( !bStrict && Type == EJson::Null ) + return true; + + return false; + } + else if ( !Value.JsonValue.IsValid() ) + { + EJson Type = JsonValue->Type; + if ( Type == EJson::None ) + return true; + if ( !bStrict && Type == EJson::Null ) + return true; + + return false; + } + + if ( JsonValue == Value.JsonValue ) + return true; + + if ( JsonValue->Type == Value.JsonValue->Type ) + { + switch ( JsonValue->Type ) + { + case EJson::None: return true; + case EJson::Null: return true; + case EJson::Boolean: return JsonValue->AsBool() == Value.JsonValue->AsBool(); + case EJson::Number: return JsonValue->AsNumber() == Value.JsonValue->AsNumber(); + case EJson::String: return JsonValue->AsString() == Value.JsonValue->AsString(); + case EJson::Object: + { + const TSharedPtr<FJsonObject>& JsonA = JsonValue->AsObject(); + const TSharedPtr<FJsonObject>& JsonB = Value.JsonValue->AsObject(); + if ( JsonA.IsValid() && JsonB.IsValid() ) + return JsonA == JsonB; + } + case EJson::Array: + { + const TArray<TSharedPtr<FJsonValue>>* JsonA; + const TArray<TSharedPtr<FJsonValue>>* JsonB; + if ( JsonValue->TryGetArray( JsonA ) && Value.JsonValue->TryGetArray( JsonB ) ) + return JsonA == JsonB; + } + } + + return false; + } + + if ( bStrict ) + return false; + + EJson TypeA = JsonValue->Type; + EJson TypeB = Value.JsonValue->Type; + + if ( TypeA == EJson::None || TypeA == EJson::Null ) + { + if ( TypeB == EJson::None || TypeB == EJson::Null ) + return true; + } + + if ( TypeA == EJson::Number && TypeB == EJson::String ) + return JsonValue->AsNumber() == Value.GetNumber(); + if ( TypeA == EJson::String && TypeB == EJson::Number ) + return GetNumber() == Value.JsonValue->AsNumber(); + + if ( TypeA == EJson::Boolean ) + { + if ( TypeB == EJson::Number ) + return GetNumber() == Value.JsonValue->AsNumber(); + if ( TypeB == EJson::String ) + return GetNumber() == Value.GetNumber(); + } + + if ( TypeB == EJson::Boolean ) + { + if ( TypeA == EJson::Number ) + return JsonValue->AsNumber() == Value.GetNumber(); + if ( TypeA == EJson::String ) + return GetNumber() == Value.GetNumber(); + } + + if ( TypeA == EJson::Object && TypeB == EJson::Object ) + { + if ( IsRotator() && Value.IsRotator() ) + return GetRotator().Equals( Value.GetRotator() ); + if ( IsTransform() && Value.IsTransform() ) + return GetTransform().Equals( Value.GetTransform() ); + if ( IsVector() && Value.IsVector() ) + return GetVector().Equals( Value.GetVector() ); + } + + return false; +} + +bool FJsonLibraryValue::GetBoolean() const +{ + if ( !JsonValue.IsValid() ) + return false; + + switch ( JsonValue->Type ) + { + case EJson::Boolean: return JsonValue->AsBool(); + case EJson::Number: return JsonValue->AsNumber() != 0.0; + case EJson::String: return JsonValue->AsString().ToBool(); + } + + return false; +} + +float FJsonLibraryValue::GetFloat() const +{ + return (float)GetNumber(); +} + +double FJsonLibraryValue::GetNumber() const +{ + if ( !JsonValue.IsValid() ) + return 0.0; + + switch ( JsonValue->Type ) + { + case EJson::Boolean: return JsonValue->AsBool() ? 1.0 : 0.0; + case EJson::Number: return JsonValue->AsNumber(); + case EJson::String: + { + const FString& Value = JsonValue->AsString(); + return Value.IsNumeric() ? FCString::Atod( *Value ) : 0.0; + } + } + + return 0.0; +} + +int32 FJsonLibraryValue::GetInteger() const +{ + return (int32)GetNumber(); +} + +FString FJsonLibraryValue::GetString() const +{ + if ( !JsonValue.IsValid() ) + return FString(); + + switch ( JsonValue->Type ) + { + case EJson::Boolean: return JsonValue->AsBool() ? TEXT( "true" ) : TEXT( "false" ); + case EJson::Number: return FString::SanitizeFloat( JsonValue->AsNumber(), 0 ); + case EJson::String: return JsonValue->AsString(); + } + + return FString(); +} + +FDateTime FJsonLibraryValue::GetDateTime() const +{ + if ( !JsonValue.IsValid() || JsonValue->Type != EJson::String ) + return FDateTime(); + + FDateTime DateTime; + if ( FDateTime::ParseIso8601( *JsonValue->AsString(), DateTime ) ) + return DateTime; + + return FDateTime(); +} + +FGuid FJsonLibraryValue::GetGuid() const +{ + if ( !JsonValue.IsValid() || JsonValue->Type != EJson::String ) + return FGuid(); + + FGuid Guid; + if ( FGuid::Parse( JsonValue->AsString(), Guid ) ) + return Guid; + + return FGuid(); +} + +FColor FJsonLibraryValue::GetColor() const +{ + if ( !JsonValue.IsValid() || JsonValue->Type != EJson::String ) + return FColor(); + + if ( IsColor() ) + return FColor::FromHex( JsonValue->AsString() ); + + return FColor(); +} + +FLinearColor FJsonLibraryValue::GetLinearColor() const +{ + if ( GetType() == EJsonLibraryType::Object ) + return GetObject().ToLinearColor(); + + return FLinearColor(); +} + +FRotator FJsonLibraryValue::GetRotator() const +{ + if ( GetType() == EJsonLibraryType::Object ) + return GetObject().ToRotator(); + + return FRotator::ZeroRotator; +} + +FTransform FJsonLibraryValue::GetTransform() const +{ + if ( GetType() == EJsonLibraryType::Object ) + return GetObject().ToTransform(); + + return FTransform::Identity; +} + +FVector FJsonLibraryValue::GetVector() const +{ + if ( GetType() == EJsonLibraryType::Object ) + return GetObject().ToVector(); + + return FVector::ZeroVector; +} + +FJsonLibraryObject FJsonLibraryValue::GetObject() const +{ + return FJsonLibraryObject( JsonValue ); +} + +FJsonLibraryList FJsonLibraryValue::GetList() const +{ + return FJsonLibraryList( JsonValue ); +} + +int8 FJsonLibraryValue::GetInt8() const +{ + return (int16)GetNumber(); +} + +uint8 FJsonLibraryValue::GetUInt8() const +{ + return (uint16)GetNumber(); +} + +int16 FJsonLibraryValue::GetInt16() const +{ + return (int16)GetNumber(); +} + +uint16 FJsonLibraryValue::GetUInt16() const +{ + return (uint16)GetNumber(); +} + +int32 FJsonLibraryValue::GetInt32() const +{ + return (int32)GetNumber(); +} + +uint32 FJsonLibraryValue::GetUInt32() const +{ + return (uint32)GetNumber(); +} + +int64 FJsonLibraryValue::GetInt64() const +{ + return (int64)GetNumber(); +} + +uint64 FJsonLibraryValue::GetUInt64() const +{ + return (uint64)GetNumber(); +} + +bool FJsonLibraryValue::TryParse( const FString& Text, bool bStripComments /*= false*/, bool bStripTrailingCommas /*= false*/ ) +{ + if ( Text.IsEmpty() ) + return false; + + FString TrimmedText = Text; + TrimmedText.TrimStartInline(); + TrimmedText.TrimEndInline(); + + if ( bStripComments || bStripTrailingCommas ) + TrimmedText = UJsonLibraryHelpers::StripCommentsOrCommas( TrimmedText, bStripComments, bStripTrailingCommas ); + + if ( ( TrimmedText.StartsWith( "{" ) && TrimmedText.EndsWith( "}" ) ) + || ( TrimmedText.StartsWith( "[" ) && TrimmedText.EndsWith( "]" ) ) ) + { + // deserialize object or array + TSharedRef<TJsonReader<TCHAR>> Reader = TJsonReaderFactory<TCHAR>::Create( TrimmedText ); + if ( !FJsonSerializer::Deserialize( Reader, JsonValue ) || !JsonValue.IsValid() ) + return false; + + // check type + if ( JsonValue->Type != EJson::Object && JsonValue->Type != EJson::Array ) + { + JsonValue.Reset(); + return false; + } + } + else + { + // wrap value with array + TrimmedText = FString::Printf( TEXT( "[%s]" ), *TrimmedText ); + TSharedRef<TJsonReader<TCHAR>> Reader = TJsonReaderFactory<TCHAR>::Create( TrimmedText ); + + TArray<TSharedPtr<FJsonValue>> JsonArray; + if ( !FJsonSerializer::Deserialize( Reader, JsonArray ) || JsonArray.Num() != 1 ) + return false; + + // unwrap value + JsonValue = JsonArray[ 0 ]; + } + + return JsonValue.IsValid(); +} + +bool FJsonLibraryValue::TryStringify( FString& Text, bool bCondensed /*= true*/ ) const +{ + if ( !JsonValue.IsValid() || JsonValue->Type == EJson::None ) + return false; + + if ( JsonValue->Type == EJson::Object ) + { + if ( bCondensed ) + { + TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( JsonValue->AsObject().ToSharedRef(), Writer ) ) + return false; + } + else + { + TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( JsonValue->AsObject().ToSharedRef(), Writer ) ) + return false; + } + + Text.TrimStartInline(); + Text.TrimEndInline(); + + if ( !Text.StartsWith( "{" ) || !Text.EndsWith( "}" ) ) + return false; + } + else if ( JsonValue->Type == EJson::Array ) + { + if ( bCondensed ) + { + TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( JsonValue->AsArray(), Writer ) ) + return false; + } + else + { + TSharedRef<TJsonWriter<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TPrettyJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( JsonValue->AsArray(), Writer ) ) + return false; + } + + Text.TrimStartInline(); + Text.TrimEndInline(); + + if ( !Text.StartsWith( "[" ) || !Text.EndsWith( "]" ) ) + return false; + } + else + { + // wrap value with array + TArray<TSharedPtr<FJsonValue>> JsonArray; + JsonArray.Add( JsonValue ); + + if ( bCondensed ) + { + TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( JsonArray, Writer ) ) + return false; + } + else + { + TSharedRef<TJsonWriter<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>> Writer = TJsonWriterFactory<TCHAR, TCondensedJsonPrintPolicy<TCHAR>>::Create( &Text ); + if ( !FJsonSerializer::Serialize( JsonArray, Writer ) ) + return false; + } + + Text.TrimStartInline(); + Text.TrimEndInline(); + + if ( !Text.StartsWith( "[" ) || !Text.EndsWith( "]" ) ) + return false; + + int32 Length = Text.Len(); + if ( Length > 2 ) + { + // trim array brackets + Text = Text.Mid( 1, Length - 2 ); + Text.TrimStartInline(); + Text.TrimEndInline(); + } + else + Text = FString(); + } + + return true; +} + +bool FJsonLibraryValue::IsValid() const +{ + return GetType() != EJsonLibraryType::Invalid; +} + +bool FJsonLibraryValue::IsDateTime() const +{ + if ( !JsonValue.IsValid() || JsonValue->Type != EJson::String ) + return false; + + FDateTime DateTime; + return FDateTime::ParseIso8601( *JsonValue->AsString(), DateTime ); +} + +bool FJsonLibraryValue::IsGuid() const +{ + if ( !JsonValue.IsValid() || JsonValue->Type != EJson::String ) + return false; + + FGuid Guid; + if ( FGuid::Parse( JsonValue->AsString(), Guid ) ) + return Guid.IsValid(); + + return false; +} + +bool FJsonLibraryValue::IsColor() const +{ + if ( !JsonValue.IsValid() || JsonValue->Type != EJson::String ) + return false; + + FString HexString = JsonValue->AsString(); + if ( HexString.IsEmpty() ) + return false; + + int32 StartIndex = HexString[ 0 ] == TCHAR( '#' ) ? 1 : 0; + if ( HexString.Len() == 3 + StartIndex ) + { + for ( int32 i = 0; i < 3; i++ ) + if ( !FChar::IsHexDigit( HexString[ StartIndex++ ] ) ) + return false; + + return true; + } + + if ( HexString.Len() == 6 + StartIndex ) + { + for ( int32 i = 0; i < 6; i++ ) + if ( !FChar::IsHexDigit( HexString[ StartIndex++ ] ) ) + return false; + + return true; + } + + if ( HexString.Len() == 8 + StartIndex ) + { + for ( int32 i = 0; i < 8; i++ ) + if ( !FChar::IsHexDigit( HexString[ StartIndex++ ] ) ) + return false; + + return true; + } + + return false; +} + +bool FJsonLibraryValue::IsLinearColor() const +{ + if ( GetType() == EJsonLibraryType::Object ) + return GetObject().IsLinearColor(); + + return false; +} + +bool FJsonLibraryValue::IsRotator() const +{ + if ( GetType() == EJsonLibraryType::Object ) + return GetObject().IsRotator(); + + return false; +} + +bool FJsonLibraryValue::IsTransform() const +{ + if ( GetType() == EJsonLibraryType::Object ) + return GetObject().IsTransform(); + + return false; +} + +bool FJsonLibraryValue::IsVector() const +{ + if ( GetType() == EJsonLibraryType::Object ) + return GetObject().IsVector(); + + return false; +} + +FJsonLibraryValue FJsonLibraryValue::Parse( const FString& Text ) +{ + FJsonLibraryValue Value = TSharedPtr<FJsonValue>(); + if ( !Value.TryParse( Text ) ) + Value.JsonValue.Reset(); + + return Value; +} + +FJsonLibraryValue FJsonLibraryValue::ParseRelaxed( const FString& Text, bool bStripComments /*= true*/, bool bStripTrailingCommas /*= true*/ ) +{ + FJsonLibraryValue Value = TSharedPtr<FJsonValue>(); + if ( !Value.TryParse( Text, bStripComments, bStripTrailingCommas ) ) + Value.JsonValue.Reset(); + + return Value; +} + +FString FJsonLibraryValue::Stringify( bool bCondensed /*= true*/ ) const +{ + FString Text; + if ( TryStringify( Text, bCondensed ) ) + return Text; + + return FString(); +} + +TArray<FJsonLibraryValue> FJsonLibraryValue::ToArray() const +{ + return FJsonLibraryList( JsonValue ).ToArray(); +} + +TMap<FString, FJsonLibraryValue> FJsonLibraryValue::ToMap() const +{ + return FJsonLibraryObject( JsonValue ).ToMap(); +} + +bool FJsonLibraryValue::operator==( const FJsonLibraryValue& Value ) const +{ + return Equals( Value ); +} + +bool FJsonLibraryValue::operator!=( const FJsonLibraryValue& Value ) const +{ + return !Equals( Value ); +} + +bool FJsonLibraryValue::operator==( const FJsonLibraryObject& Object ) const +{ + return Equals( FJsonLibraryValue( Object ) ); +} + +bool FJsonLibraryValue::operator!=( const FJsonLibraryObject& Object ) const +{ + return !Equals( FJsonLibraryValue( Object ) ); +} + +bool FJsonLibraryValue::operator==( const FJsonLibraryList& List ) const +{ + return Equals( FJsonLibraryValue( List ) ); +} + +bool FJsonLibraryValue::operator!=( const FJsonLibraryList& List ) const +{ + return !Equals( FJsonLibraryValue( List ) ); +} diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibrary.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..33db10b46fd34340b7ec67d7878306a762cec9c8 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibrary.h @@ -0,0 +1,7 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "JsonLibraryEnums.h" +#include "JsonLibraryValue.h" +#include "JsonLibraryObject.h" +#include "JsonLibraryList.h" +#include "JsonLibraryHelpers.h" diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryBlueprintHelpers.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryBlueprintHelpers.h new file mode 100644 index 0000000000000000000000000000000000000000..e845b2b86258cda45e39718710f45441ccd2cfcd --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryBlueprintHelpers.h @@ -0,0 +1,78 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "JsonLibraryObject.h" +#include "JsonLibraryBlueprintHelpers.generated.h" + +USTRUCT(BlueprintInternalUseOnly) +struct FStructBase +{ + GENERATED_USTRUCT_BODY() + + FStructBase() + { + } + + virtual ~FStructBase() + { + } +}; + +UCLASS() +class JSONLIBRARY_API UJsonLibraryBlueprintHelpers : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, CustomThunk, Category = "JSON Library", meta=(CustomStructureParam = "OutStruct", BlueprintInternalUseOnly="true")) + static bool StructFromJson( const UScriptStruct* StructType, const FJsonLibraryObject& Object, FStructBase& OutStruct ); + UFUNCTION(BlueprintCallable, CustomThunk, Category = "JSON Library", meta=(CustomStructureParam = "Struct", BlueprintInternalUseOnly="true")) + static FJsonLibraryObject StructToJson( const UScriptStruct* StructType, const FStructBase& Struct ); + + UFUNCTION(BlueprintCallable, Category = "JSON Library", meta=(BlueprintInternalUseOnly="true")) + static FJsonLibraryObject ConstructInvalidObject(); + UFUNCTION(BlueprintPure, Category = "JSON Library", meta=(BlueprintInternalUseOnly="true")) + static bool IsValidObject( const FJsonLibraryObject& Object ); + + static bool Generic_StructFromJson( const UScriptStruct* StructType, const FJsonLibraryObject& Object, void* OutStructPtr ); + DECLARE_FUNCTION( execStructFromJson ) + { + P_GET_OBJECT( UScriptStruct, StructType ); + P_GET_STRUCT( FJsonLibraryObject, Object ); + + Stack.StepCompiledIn<FStructProperty>( NULL ); + void* OutStructPtr = Stack.MostRecentPropertyAddress; + + P_FINISH; + bool bSuccess = false; + + P_NATIVE_BEGIN; + bSuccess = Generic_StructFromJson( StructType, Object, OutStructPtr ); + P_NATIVE_END; + + *(bool*)RESULT_PARAM = bSuccess; + } + + static bool Generic_StructToJson( const UScriptStruct* StructType, void* StructPtr, FJsonLibraryObject& OutObject ); + DECLARE_FUNCTION( execStructToJson ) + { + P_GET_OBJECT( UScriptStruct, StructType ); + + Stack.StepCompiledIn<FStructProperty>( NULL ); + void* StructPtr = Stack.MostRecentPropertyAddress; + + P_FINISH; + bool bSuccess = false; + FJsonLibraryObject OutObject = ConstructInvalidObject(); + + P_NATIVE_BEGIN; + bSuccess = Generic_StructToJson( StructType, StructPtr, OutObject ); + P_NATIVE_END; + + *(FJsonLibraryObject*)RESULT_PARAM = bSuccess ? OutObject : ConstructInvalidObject(); + } + + static bool InitializeStructData( const FJsonLibraryObject& Object, const UScriptStruct* StructType, FStructOnScope& StructData ); +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryConverter.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryConverter.h new file mode 100644 index 0000000000000000000000000000000000000000..a9a48daf9cd755ee9584bf5c92a60c987dc36ace --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryConverter.h @@ -0,0 +1,340 @@ +// Engine/Source/Runtime/JsonUtilities/Public/JsonObjectConverter.h + +#pragma once + +#include "Containers/Array.h" +#include "Containers/Map.h" +#include "Containers/UnrealString.h" +#include "CoreMinimal.h" +#include "CoreTypes.h" +#include "Delegates/Delegate.h" +#include "Dom/JsonObject.h" +#include "Dom/JsonValue.h" +#include "Internationalization/Text.h" +#include "JsonGlobals.h" +#include "JsonObjectWrapper.h" +#include "Logging/LogCategory.h" +#include "Logging/LogMacros.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonTypes.h" +#include "Serialization/JsonWriter.h" +#include "Templates/SharedPointer.h" +#include "Trace/Detail/Channel.h" +#include "UObject/Class.h" + +class FProperty; +class UStruct; + +/** Class that handles converting Json objects to and from UStructs */ +class JSONLIBRARY_API FJsonLibraryConverter +{ +public: + + /** FName case insensitivity can make the casing of UPROPERTIES unpredictable. Attempt to standardize output. */ + static FString StandardizeCase(const FString &StringIn); + + /** Parse an FText from a json object (assumed to be of the form where keys are culture codes and values are strings) */ + static bool GetTextFromObject(const TSharedRef<FJsonObject>& Obj, FText& TextOut); + + /** Convert a Json value to text (takes some hints from the value name) */ + static bool GetTextFromField(const FString& FieldName, const TSharedPtr<FJsonValue>& FieldValue, FText& TextOut); + +public: // UStruct -> JSON + + /** + * Optional callback that will be run when exporting a single property to Json. + * If this returns a valid value it will be inserted into the export chain. + * If this returns nullptr or is not bound, it will try generic type-specific export behavior before falling back to outputting ExportText as a string. + */ + DECLARE_DELEGATE_RetVal_TwoParams(TSharedPtr<FJsonValue>, CustomExportCallback, FProperty* /* Property */, const void* /* Value */); + + /** + * Utility Export Callback for having object properties expanded to full Json. + */ + UE_DEPRECATED(4.25, "ObjectJsonCallback has been deprecated - please remove the usage of it from your project") + static TSharedPtr<FJsonValue> ObjectJsonCallback(FProperty* Property , const void* Value); + + /** + * Templated version of UStructToJsonObject to try and make most of the params. Also serves as an example use case + * + * @param InStruct The UStruct instance to read from + * @param ExportCb Optional callback to override export behavior, if this returns null it will fallback to the default + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @return FJsonObject pointer. Invalid if an error occurred. + */ + template<typename InStructType> + static TSharedPtr<FJsonObject> UStructToJsonObject(const InStructType& InStruct, int64 CheckFlags = 0, int64 SkipFlags = 0, const CustomExportCallback* ExportCb = nullptr) + { + TSharedRef<FJsonObject> JsonObject = MakeShared<FJsonObject>(); + if (UStructToJsonObject(InStructType::StaticStruct(), &InStruct, JsonObject, CheckFlags, SkipFlags, ExportCb)) + { + return JsonObject; + } + return TSharedPtr<FJsonObject>(); // something went wrong + } + + /** + * Converts from a UStruct to a Json Object, using exportText + * + * @param StructDefinition UStruct definition that is looked over for properties + * @param Struct The UStruct instance to copy out of + * @param JsonObject Json Object to be filled in with data from the ustruct + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param ExportCb Optional callback to override export behavior, if this returns null it will fallback to the default + * + * @return False if any properties failed to write + */ + static bool UStructToJsonObject(const UStruct* StructDefinition, const void* Struct, TSharedRef<FJsonObject> OutJsonObject, int64 CheckFlags = 0, int64 SkipFlags = 0, const CustomExportCallback* ExportCb = nullptr); + + /** + * Converts from a UStruct to a json string containing an object, using exportText + * + * @param StructDefinition UStruct definition that is looked over for properties + * @param Struct The UStruct instance to copy out of + * @param JsonObject Json Object to be filled in with data from the ustruct + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param Indent How many tabs to add to the json serializer + * @param ExportCb Optional callback to override export behavior, if this returns null it will fallback to the default + * @param bPrettyPrint Option to use pretty print (e.g., adds line endings) or condensed print + * + * @return False if any properties failed to write + */ + static bool UStructToJsonObjectString(const UStruct* StructDefinition, const void* Struct, FString& OutJsonString, int64 CheckFlags = 0, int64 SkipFlags = 0, int32 Indent = 0, const CustomExportCallback* ExportCb = nullptr, bool bPrettyPrint = true); + + /** + * Templated version; Converts from a UStruct to a json string containing an object, using exportText + * + * @param InStruct The UStruct instance to copy out of + * @param OutJsonString Json Object to be filled in with data from the ustruct + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param Indent How many tabs to add to the json serializer + * @param ExportCb Optional callback to override export behavior, if this returns null it will fallback to the default + * @param bPrettyPrint Option to use pretty print (e.g., adds line endings) or condensed print + * + * @return False if any properties failed to write + */ + template<typename InStructType> + static bool UStructToJsonObjectString(const InStructType& InStruct, FString& OutJsonString, int64 CheckFlags = 0, int64 SkipFlags = 0, int32 Indent = 0, const CustomExportCallback* ExportCb = nullptr, bool bPrettyPrint = true) + { + return UStructToJsonObjectString(InStructType::StaticStruct(), &InStruct, OutJsonString, CheckFlags, SkipFlags, Indent, ExportCb, bPrettyPrint); + } + + /** + * Wrapper to UStructToJsonObjectString that allows a print policy to be specified. + */ + template<typename CharType, template<typename> class PrintPolicy> + static bool UStructToFormattedJsonObjectString(const UStruct* StructDefinition, const void* Struct, FString& OutJsonString, int64 CheckFlags = 0, int64 SkipFlags = 0, int32 Indent = 0, const CustomExportCallback* ExportCb = nullptr) + { + TSharedRef<FJsonObject> JsonObject = MakeShareable(new FJsonObject()); + if (UStructToJsonObject(StructDefinition, Struct, JsonObject, CheckFlags, SkipFlags, ExportCb)) + { + TSharedRef<TJsonWriter<CharType, PrintPolicy<CharType>>> JsonWriter = TJsonWriterFactory<CharType, PrintPolicy<CharType>>::Create(&OutJsonString, Indent); + + if (FJsonSerializer::Serialize(JsonObject, JsonWriter)) + { + JsonWriter->Close(); + return true; + } + else + { + UE_LOG(LogJson, Warning, TEXT("UStructToFormattedObjectString - Unable to write out json")); + JsonWriter->Close(); + } + } + + return false; + } + + /** + * Converts from a UStruct to a set of json attributes (possibly from within a JsonObject) + * + * @param StructDefinition UStruct definition that is looked over for properties + * @param Struct The UStruct instance to copy out of + * @param OutJsonAttributes Map of attributes to copy in to + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param ExportCb Optional callback to override export behavior, if this returns null it will fallback to the default + * + * @return False if any properties failed to write + */ + static bool UStructToJsonAttributes(const UStruct* StructDefinition, const void* Struct, TMap< FString, TSharedPtr<FJsonValue> >& OutJsonAttributes, int64 CheckFlags = 0, int64 SkipFlags = 0, const CustomExportCallback* ExportCb = nullptr); + + /* * Converts from a FProperty to a Json Value using exportText + * + * @param Property The property to export + * @param Value Pointer to the value of the property + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param ExportCb Optional callback to override export behavior, if this returns null it will fallback to the default + * @param OuterProperty If applicable, the Array/Set/Map Property that contains this property + * + * @return The constructed JsonValue from the property + */ + static TSharedPtr<FJsonValue> UPropertyToJsonValue(FProperty* Property, const void* Value, int64 CheckFlags = 0, int64 SkipFlags = 0, const CustomExportCallback* ExportCb = nullptr, FProperty* OuterProperty = nullptr); + +public: // JSON -> UStruct + + /** + * Converts from a Json Object to a UStruct, using importText + * + * @param JsonObject Json Object to copy data out of + * @param StructDefinition UStruct definition that is looked over for properties + * @param OutStruct The UStruct instance to copy in to + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param bStrictMode Whether to strictly check the json attributes + * @param OutFailReason Reason of the failure if any + * + * @return False if any properties matched but failed to deserialize + */ + static bool JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags = 0, int64 SkipFlags = 0, const bool bStrictMode = false, FText* OutFailReason = nullptr); + + /** + * Templated version of JsonObjectToUStruct + * + * @param JsonObject Json Object to copy data out of + * @param OutStruct The UStruct instance to copy in to + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param bStrictMode Whether to strictly check the json attributes + * @param OutFailReason Reason of the failure if any + * + * @return False if any properties matched but failed to deserialize + */ + template<typename OutStructType> + static bool JsonObjectToUStruct(const TSharedRef<FJsonObject>& JsonObject, OutStructType* OutStruct, int64 CheckFlags = 0, int64 SkipFlags = 0, const bool bStrictMode = false, FText* OutFailReason = nullptr) + { + return JsonObjectToUStruct(JsonObject, OutStructType::StaticStruct(), OutStruct, CheckFlags, SkipFlags, bStrictMode, OutFailReason); + } + + /** + * Converts a set of json attributes (possibly from within a JsonObject) to a UStruct, using importText + * + * @param JsonAttributes Json Object to copy data out of + * @param StructDefinition UStruct definition that is looked over for properties + * @param OutStruct The UStruct instance to copy in to + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param bStrictMode Whether to strictly check the json attributes + * @param OutFailReason Reason of the failure if any + * + * @return False if any properties matched but failed to deserialize + */ + static bool JsonAttributesToUStruct(const TMap< FString, TSharedPtr<FJsonValue> >& JsonAttributes, const UStruct* StructDefinition, void* OutStruct, int64 CheckFlags = 0, int64 SkipFlags = 0, const bool bStrictMode = false, FText* OutFailReason = nullptr); + + /** + * Converts a single JsonValue to the corresponding FProperty (this may recurse if the property is a UStruct for instance). + * + * @param JsonValue The value to assign to this property + * @param Property The FProperty definition of the property we're setting. + * @param OutValue Pointer to the property instance to be modified. + * @param CheckFlags Only convert sub-properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip sub-properties that match any of these flags + * @param bStrictMode Whether to strictly check the json attributes + * @param OutFailReason Reason of the failure if any + * + * @return False if the property failed to serialize + */ + static bool JsonValueToUProperty(const TSharedPtr<FJsonValue>& JsonValue, FProperty* Property, void* OutValue, int64 CheckFlags = 0, int64 SkipFlags = 0, const bool bStrictMode = false, FText* OutFailReason = nullptr); + + /** + * Converts from a json string containing an object to a UStruct + * + * @param JsonString String containing JSON formatted data. + * @param OutStruct The UStruct instance to copy in to + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags + * @param bStrictMode Whether to strictly check the json attributes + * + * @return False if any properties matched but failed to deserialize + */ + template<typename OutStructType> + static bool JsonObjectStringToUStruct(const FString& JsonString, OutStructType* OutStruct, int64 CheckFlags = 0, int64 SkipFlags = 0, const bool bStrictMode = false) + { + TSharedPtr<FJsonObject> JsonObject; + TSharedRef<TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(JsonString); + if (!FJsonSerializer::Deserialize(JsonReader, JsonObject) || !JsonObject.IsValid()) + { + UE_LOG(LogJson, Warning, TEXT("JsonObjectStringToUStruct - Unable to parse json=[%s]"), *JsonString); + return false; + } + if (!FJsonLibraryConverter::JsonObjectToUStruct(JsonObject.ToSharedRef(), OutStruct, CheckFlags, SkipFlags, bStrictMode)) + { + UE_LOG(LogJson, Warning, TEXT("JsonObjectStringToUStruct - Unable to deserialize. json=[%s]"), *JsonString); + return false; + } + return true; + } + + /** + * Converts from a json string containing an array to an array of UStructs + * + * @param JsonString String containing JSON formatted data. + * @param OutStructArray The UStruct array to copy in to + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags. + * @param bStrictMode Whether to strictly check the json attributes + * + * @return False if any properties matched but failed to deserialize. + */ + template<typename OutStructType> + static bool JsonArrayStringToUStruct(const FString& JsonString, TArray<OutStructType>* OutStructArray, int64 CheckFlags = 0, int64 SkipFlags = 0, const bool bStrictMode = false) + { + TArray<TSharedPtr<FJsonValue> > JsonArray; + TSharedRef<TJsonReader<> > JsonReader = TJsonReaderFactory<>::Create(JsonString); + if (!FJsonSerializer::Deserialize(JsonReader, JsonArray)) + { + UE_LOG(LogJson, Warning, TEXT("JsonArrayStringToUStruct - Unable to parse. json=[%s]"), *JsonString); + return false; + } + if (!JsonArrayToUStruct(JsonArray, OutStructArray, CheckFlags, SkipFlags, bStrictMode)) + { + UE_LOG(LogJson, Warning, TEXT("JsonArrayStringToUStruct - Error parsing one of the elements. json=[%s]"), *JsonString); + return false; + } + return true; + } + + /** + * Converts from an array of json values to an array of UStructs. + * + * @param JsonArray Array containing json values to convert. + * @param OutStructArray The UStruct array to copy in to + * @param CheckFlags Only convert properties that match at least one of these flags. If 0 check all properties. + * @param SkipFlags Skip properties that match any of these flags. + * @param bStrictMode Whether to strictly check the json attributes + * + * @return False if any of the matching elements are not an object, or if one of the matching elements could not be converted to the specified UStruct type. + */ + template<typename OutStructType> + static bool JsonArrayToUStruct(const TArray<TSharedPtr<FJsonValue>>& JsonArray, TArray<OutStructType>* OutStructArray, int64 CheckFlags = 0, int64 SkipFlags = 0, const bool bStrictMode = false) + { + OutStructArray->SetNum(JsonArray.Num()); + for (int32 i = 0; i < JsonArray.Num(); ++i) + { + const auto& Value = JsonArray[i]; + if (Value->Type != EJson::Object) + { + UE_LOG(LogJson, Warning, TEXT("JsonArrayToUStruct - Array element [%i] was not an object."), i); + return false; + } + if (!FJsonLibraryConverter::JsonObjectToUStruct(Value->AsObject().ToSharedRef(), OutStructType::StaticStruct(), &(*OutStructArray)[i], CheckFlags, SkipFlags, bStrictMode)) + { + UE_LOG(LogJson, Warning, TEXT("JsonArrayToUStruct - Unable to convert element [%i]."), i); + return false; + } + } + return true; + } + + /* + * Parses text arguments from Json into a map + * @param JsonObject Object to parse arguments from + */ + static FFormatNamedArguments ParseTextArgumentsFromJson(const TSharedPtr<const FJsonObject>& JsonObject); +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryEnums.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryEnums.h new file mode 100644 index 0000000000000000000000000000000000000000..7813489a6965dc6984945932ff9fe91bb1b805da --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryEnums.h @@ -0,0 +1,25 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "JsonLibraryEnums.generated.h" + +UENUM(BlueprintType, meta = (DisplayName = "JSON Type")) +enum class EJsonLibraryType : uint8 +{ + Invalid UMETA(DisplayName="Invalid"), + Null UMETA(DisplayName="Null"), + Object UMETA(DisplayName="Object"), + Array UMETA(DisplayName="Array"), + Boolean UMETA(DisplayName="Boolean"), + Number UMETA(DisplayName="Number"), + String UMETA(DisplayName="String") +}; + +UENUM(BlueprintType, meta = (DisplayName = "JSON Notify")) +enum class EJsonLibraryNotifyAction : uint8 +{ + None UMETA(DisplayName="None"), + Added UMETA(DisplayName="Added"), + Removed UMETA(DisplayName="Removed"), + Changed UMETA(DisplayName="Changed"), + Reset UMETA(DisplayName="Reset") +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryHelpers.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryHelpers.h new file mode 100644 index 0000000000000000000000000000000000000000..50ee6d98f688253dbbd7dca56bef4aa98ac9420e --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryHelpers.h @@ -0,0 +1,1087 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "JsonLibraryValue.h" +#include "JsonLibraryObject.h" +#include "JsonLibraryList.h" +#include "JsonLibraryHelpers.generated.h" + +UCLASS() +class JSONLIBRARY_API UJsonLibraryHelpers : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +#if UE_EDITOR +protected: +#else +public: +#endif + + // Parse a JSON string. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Parse", AdvancedDisplay = "bComments,bTrailingCommas"), Category = "JSON Library") + static FJsonLibraryValue Parse( const FString& Text, bool bComments = false, bool bTrailingCommas = false ); + + // Parse a JSON object string. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Parse Object", AutoCreateRefTerm = "Notify", AdvancedDisplay = "Notify"), Category = "JSON Library|Object") + static FJsonLibraryObject ParseObject( const FString& Text, const FJsonLibraryObjectNotify& Notify ); + // Parse a JSON array string. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Parse List", AutoCreateRefTerm = "Notify", AdvancedDisplay = "Notify"), Category = "JSON Library|List") + static FJsonLibraryList ParseList( const FString& Text, const FJsonLibraryListNotify& Notify ); + + // Construct a JSON null. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Construct null", CompactNodeTitle = "null"), Category = "JSON Library") + static FJsonLibraryValue ConstructNull(); + + // Construct a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct Object", CompactNodeTitle = "JSON", AutoCreateRefTerm = "Notify"), Category = "JSON Library") + static FJsonLibraryObject ConstructObject( const FJsonLibraryObjectNotify& Notify ); + // Construct a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct List", CompactNodeTitle = "JSON", AutoCreateRefTerm = "Notify"), Category = "JSON Library") + static FJsonLibraryList ConstructList( const FJsonLibraryListNotify& Notify ); + + // Construct an array of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct Array", CompactNodeTitle = "ARRAY"), Category = "JSON Library|Array") + static TArray<FJsonLibraryValue> ConstructArray(); + // Construct a map of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Construct Map", CompactNodeTitle = "MAP"), Category = "JSON Library|Map") + static TMap<FString, FJsonLibraryValue> ConstructMap(); + + // Convert a boolean to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Boolean", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FJsonLibraryValue FromBoolean( bool Value ); + // Convert a float to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Float", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FJsonLibraryValue FromFloat( float Value ); + // Convert an integer to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Integer", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FJsonLibraryValue FromInteger( int32 Value ); + // Convert a string to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From String", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FJsonLibraryValue FromString( const FString& Value ); + + // Convert a date/time to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Date/Time", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryValue FromDateTime( const FDateTime& Value ); + // Convert a GUID to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From GUID", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryValue FromGuid( const FGuid& Value ); + + // Convert a color to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Color", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryValue FromColor( const FColor& Value ); + // Convert a linear color to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Linear Color", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryValue FromLinearColor( const FLinearColor& Value ); + + // Convert a rotator to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Rotator", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryValue FromRotator( const FRotator& Value ); + // Convert a transform to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Transform", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryValue FromTransform( const FTransform& Value ); + // Convert a vector to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Vector", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryValue FromVector( const FVector& Value ); + + // Convert a JSON object to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From Object", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FJsonLibraryValue FromObject( UPARAM(ref) const FJsonLibraryObject& Value ); + // Convert a JSON array to a JSON value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert From List", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FJsonLibraryValue FromList( UPARAM(ref) const FJsonLibraryList& Value ); + + // Copy an array of JSON values to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Array"), Category = "JSON Library|Array") + static FJsonLibraryValue FromArray( const TArray<FJsonLibraryValue>& Value ); + // Copy a map of JSON values to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Map"), Category = "JSON Library|Map") + static FJsonLibraryValue FromMap( const TMap<FString, FJsonLibraryValue>& Value ); + + // Copy an array of booleans to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Boolean Array"), Category = "JSON Library|Array") + static FJsonLibraryValue FromBooleanArray( const TArray<bool>& Value ); + // Copy an array of floats to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Float Array"), Category = "JSON Library|Array") + static FJsonLibraryValue FromFloatArray( const TArray<float>& Value ); + // Copy an array of integers to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Integer Array"), Category = "JSON Library|Array") + static FJsonLibraryValue FromIntegerArray( const TArray<int32>& Value ); + // Copy an array of strings to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From String Array"), Category = "JSON Library|Array") + static FJsonLibraryValue FromStringArray( const TArray<FString>& Value ); + + // Copy an array of date/times to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Date/Time Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryValue FromDateTimeArray( const TArray<FDateTime>& Value ); + // Copy an array of GUIDs to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From GUID Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryValue FromGuidArray( const TArray<FGuid>& Value ); + + // Copy an array of colors to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Color Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryValue FromColorArray( const TArray<FColor>& Value ); + // Copy an array of linear colors to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Linear Color Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryValue FromLinearColorArray( const TArray<FLinearColor>& Value ); + + // Copy an array of rotators to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Rotator Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryValue FromRotatorArray( const TArray<FRotator>& Value ); + // Copy an array of transforms to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Transform Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryValue FromTransformArray( const TArray<FTransform>& Value ); + // Copy an array of vectors to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Vector Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryValue FromVectorArray( const TArray<FVector>& Value ); + + // Copy an array of JSON objects to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Object Array"), Category = "JSON Library|Array") + static FJsonLibraryValue FromObjectArray( const TArray<FJsonLibraryObject>& Value ); + + // Copy a map of booleans to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Boolean Map"), Category = "JSON Library|Map") + static FJsonLibraryValue FromBooleanMap( const TMap<FString, bool>& Value ); + // Copy a map of floats to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Float Map"), Category = "JSON Library|Map") + static FJsonLibraryValue FromFloatMap( const TMap<FString, float>& Value ); + // Copy a map of integers to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Integer Map"), Category = "JSON Library|Map") + static FJsonLibraryValue FromIntegerMap( const TMap<FString, int32>& Value ); + // Copy a map of strings to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From String Map"), Category = "JSON Library|Map") + static FJsonLibraryValue FromStringMap( const TMap<FString, FString>& Value ); + + // Copy a map of date/times to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Date/Time Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryValue FromDateTimeMap( const TMap<FString, FDateTime>& Value ); + // Copy a map of GUIDs to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From GUID Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryValue FromGuidMap( const TMap<FString, FGuid>& Value ); + + // Copy a map of colors to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Color Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryValue FromColorMap( const TMap<FString, FColor>& Value ); + // Copy a map of linear colors to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Linear Color Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryValue FromLinearColorMap( const TMap<FString, FLinearColor>& Value ); + + // Copy a map of rotators to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Rotator Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryValue FromRotatorMap( const TMap<FString, FRotator>& Value ); + // Copy a map of transforms to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Transform Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryValue FromTransformMap( const TMap<FString, FTransform>& Value ); + // Copy a map of vectors to a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy From Vector Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryValue FromVectorMap( const TMap<FString, FVector>& Value ); + + // Convert a JSON value to a boolean. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Boolean", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static bool ToBoolean( UPARAM(ref) const FJsonLibraryValue& Value ); + // Convert a JSON value to a float. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Float", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static float ToFloat( UPARAM(ref) const FJsonLibraryValue& Value ); + // Convert a JSON value to an integer. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Integer", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static int32 ToInteger( UPARAM(ref) const FJsonLibraryValue& Value ); + // Convert a JSON value to a string. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To String", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FString ToString( UPARAM(ref) const FJsonLibraryValue& Value ); + + // Convert a JSON value to a date/time. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Date/Time", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FDateTime ToDateTime( UPARAM(ref) const FJsonLibraryValue& Value ); + // Convert a JSON value to a GUID. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To GUID", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FGuid ToGuid( UPARAM(ref) const FJsonLibraryValue& Value ); + + // Convert a JSON value to a color. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Color", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FColor ToColor( UPARAM(ref) const FJsonLibraryValue& Value ); + // Convert a JSON value to a linear color. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Linear Color", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FLinearColor ToLinearColor( UPARAM(ref) const FJsonLibraryValue& Value ); + + // Convert a JSON value to a rotator. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Rotator", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FRotator ToRotator( UPARAM(ref) const FJsonLibraryValue& Value ); + // Convert a JSON value to a transform. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Transform", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FTransform ToTransform( UPARAM(ref) const FJsonLibraryValue& Value ); + // Convert a JSON value to a vector. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Vector", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FVector ToVector( UPARAM(ref) const FJsonLibraryValue& Value ); + + // Convert a JSON value to a JSON object. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To Object", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FJsonLibraryObject ToObject( UPARAM(ref) const FJsonLibraryValue& Value ); + // Convert a JSON value to a JSON array. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert To List", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library") + static FJsonLibraryList ToList( UPARAM(ref) const FJsonLibraryValue& Value ); + + // Copy this value to an array of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Array"), Category = "JSON Library|Array") + static TArray<FJsonLibraryValue> ToArray( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to a map of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Map"), Category = "JSON Library|Map") + static TMap<FString, FJsonLibraryValue> ToMap( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to an array of booleans. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Boolean Array"), Category = "JSON Library|Array") + static TArray<bool> ToBooleanArray( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to an array of floats. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Float Array"), Category = "JSON Library|Array") + static TArray<float> ToFloatArray( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to an array of integers. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Integer Array"), Category = "JSON Library|Array") + static TArray<int32> ToIntegerArray( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to an array of strings. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To String Array"), Category = "JSON Library|Array") + static TArray<FString> ToStringArray( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to an array of date/times. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Date/Time Array"), Category = "JSON Library|Array|Engine") + static TArray<FDateTime> ToDateTimeArray( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to an array of GUIDs. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To GUID Array"), Category = "JSON Library|Array|Engine") + static TArray<FGuid> ToGuidArray( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to an array of colors. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Color Array"), Category = "JSON Library|Array|Engine") + static TArray<FColor> ToColorArray( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to an array of linear colors. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Linear Color Array"), Category = "JSON Library|Array|Engine") + static TArray<FLinearColor> ToLinearColorArray( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to an array of rotators. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Rotator Array"), Category = "JSON Library|Array|Engine") + static TArray<FRotator> ToRotatorArray( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to an array of transforms. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Transform Array"), Category = "JSON Library|Array|Engine") + static TArray<FTransform> ToTransformArray( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to an array of vectors. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Vector Array"), Category = "JSON Library|Array|Engine") + static TArray<FVector> ToVectorArray( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to an array of JSON objects. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Object Array"), Category = "JSON Library|Array") + static TArray<FJsonLibraryObject> ToObjectArray( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to a map of booleans. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Boolean Map"), Category = "JSON Library|Map") + static TMap<FString, bool> ToBooleanMap( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to a map of floats. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Float Map"), Category = "JSON Library|Map") + static TMap<FString, float> ToFloatMap( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to a map of integers. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Integer Map"), Category = "JSON Library|Map") + static TMap<FString, int32> ToIntegerMap( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to a map of strings. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To String Map"), Category = "JSON Library|Map") + static TMap<FString, FString> ToStringMap( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to a map of date/times. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Date/Time Map"), Category = "JSON Library|Map|Engine") + static TMap<FString, FDateTime> ToDateTimeMap( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to a map of GUIDs. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To GUID Map"), Category = "JSON Library|Map|Engine") + static TMap<FString, FGuid> ToGuidMap( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to a map of colors. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Color Map"), Category = "JSON Library|Map|Engine") + static TMap<FString, FColor> ToColorMap( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to a map of linear colors. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Linear Color Map"), Category = "JSON Library|Map|Engine") + static TMap<FString, FLinearColor> ToLinearColorMap( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Copy this value to a map of rotators. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Rotator Map"), Category = "JSON Library|Map|Engine") + static TMap<FString, FRotator> ToRotatorMap( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to a map of transforms. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Transform Map"), Category = "JSON Library|Map|Engine") + static TMap<FString, FTransform> ToTransformMap( UPARAM(ref) const FJsonLibraryValue& Target ); + // Copy this value to a map of vectors. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy To Vector Map"), Category = "JSON Library|Map|Engine") + static TMap<FString, FVector> ToVectorMap( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Convert a linear color to a JSON object. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert Linear Color To Object", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryObject ConvertLinearColorToObject( const FLinearColor& Value ); + + // Convert a rotator to a JSON object. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert Rotator To Object", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryObject ConvertRotatorToObject( const FRotator& Value ); + // Convert a transform to a JSON object. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert Transform To Object", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryObject ConvertTransformToObject( const FTransform& Value ); + // Convert a vector to a JSON object. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert Vector To Object", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FJsonLibraryObject ConvertVectorToObject( const FVector& Value ); + + // Convert a JSON object to a linear color. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert Object To Linear Color", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FLinearColor ConvertObjectToLinearColor( UPARAM(ref) const FJsonLibraryObject& Object ); + + // Convert a JSON object to a rotator. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert Object To Rotator", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FRotator ConvertObjectToRotator( UPARAM(ref) const FJsonLibraryObject& Object ); + // Convert a JSON object to a transform. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert Object To Transform", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FTransform ConvertObjectToTransform( UPARAM(ref) const FJsonLibraryObject& Object ); + // Convert a JSON object to a vector. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Convert Object To Vector", CompactNodeTitle = "->", BlueprintAutocast), Category = "JSON Library|Engine") + static FVector ConvertObjectToVector( UPARAM(ref) const FJsonLibraryObject& Object ); + + // Copy a map of JSON values to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Map To Object"), Category = "JSON Library|Map") + static FJsonLibraryObject ConvertMapToObject( const TMap<FString, FJsonLibraryValue>& Value ); + // Copy a JSON object to a map of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Object To Map"), Category = "JSON Library|Map") + static TMap<FString, FJsonLibraryValue> ConvertObjectToMap( UPARAM(ref) const FJsonLibraryObject& Object ); + + // Copy a map of booleans to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Boolean Map To Object"), Category = "JSON Library|Map") + static FJsonLibraryObject ConvertBooleanMapToObject( const TMap<FString, bool>& Value ); + // Copy a map of floats to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Float Map To Object"), Category = "JSON Library|Map") + static FJsonLibraryObject ConvertFloatMapToObject( const TMap<FString, float>& Value ); + // Copy a map of integers to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Integer Map To Object"), Category = "JSON Library|Map") + static FJsonLibraryObject ConvertIntegerMapToObject( const TMap<FString, int32>& Value ); + // Copy a map of strings to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy String Map To Object"), Category = "JSON Library|Map") + static FJsonLibraryObject ConvertStringMapToObject( const TMap<FString, FString>& Value ); + + // Copy a map of date/times to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Date/Time Map To Object"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject ConvertDateTimeMapToObject( const TMap<FString, FDateTime>& Value ); + // Copy a map of GUIDs to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy GUID Map To Object"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject ConvertGuidMapToObject( const TMap<FString, FGuid>& Value ); + + // Copy a map of colors to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Color Map To Object"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject ConvertColorMapToObject( const TMap<FString, FColor>& Value ); + // Copy a map of linear colors to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Linear Color Map To Object"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject ConvertLinearColorMapToObject( const TMap<FString, FLinearColor>& Value ); + + // Copy a map of rotators to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Rotator Map To Object"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject ConvertRotatorMapToObject( const TMap<FString, FRotator>& Value ); + // Copy a map of transforms to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Transform Map To Object"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject ConvertTransformMapToObject( const TMap<FString, FTransform>& Value ); + // Copy a map of vectors to a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Vector Map To Object"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject ConvertVectorMapToObject( const TMap<FString, FVector>& Value ); + + // Copy an array of JSON values to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Array To List"), Category = "JSON Library|Array") + static FJsonLibraryList ConvertArrayToList( const TArray<FJsonLibraryValue>& Value ); + // Copy a JSON array to an array of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy List To Array"), Category = "JSON Library|Array") + static TArray<FJsonLibraryValue> ConvertListToArray( UPARAM(ref) const FJsonLibraryList& List ); + + // Copy an array of booleans to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Boolean Array To List"), Category = "JSON Library|Array") + static FJsonLibraryList ConvertBooleanArrayToList( const TArray<bool>& Value ); + // Copy an array of floats to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Float Array To List"), Category = "JSON Library|Array") + static FJsonLibraryList ConvertFloatArrayToList( const TArray<float>& Value ); + // Copy an array of integers to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Integer Array To List"), Category = "JSON Library|Array") + static FJsonLibraryList ConvertIntegerArrayToList( const TArray<int32>& Value ); + // Copy an array of strings to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy String Array To List"), Category = "JSON Library|Array") + static FJsonLibraryList ConvertStringArrayToList( const TArray<FString>& Value ); + + // Copy an array of date/times to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Date/Time Array To List"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList ConvertDateTimeArrayToList( const TArray<FDateTime>& Value ); + // Copy an array of GUIDs to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy GUID Array To List"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList ConvertGuidArrayToList( const TArray<FGuid>& Value ); + + // Copy an array of colors to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Color Array To List"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList ConvertColorArrayToList( const TArray<FColor>& Value ); + // Copy an array of linear colors to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Linear Color Array To List"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList ConvertLinearColorArrayToList( const TArray<FLinearColor>& Value ); + + // Copy an array of rotators to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Rotator Array To List"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList ConvertRotatorArrayToList( const TArray<FRotator>& Value ); + // Copy an array of transforms to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Transform Array To List"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList ConvertTransformArrayToList( const TArray<FTransform>& Value ); + // Copy an array of vectors to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Vector Array To List"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList ConvertVectorArrayToList( const TArray<FVector>& Value ); + + // Copy an array of JSON objects to a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Object Array To List"), Category = "JSON Library|Array") + static FJsonLibraryList ConvertObjectArrayToList( const TArray<FJsonLibraryObject>& Value ); + + + // Get the JSON type of this value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Type"), Category = "JSON Library|Value") + static EJsonLibraryType JsonValue_GetType( UPARAM(ref) const FJsonLibraryValue& Target ); + // Check if this value equals another value. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Equals"), Category = "JSON Library|Value") + static bool JsonValue_Equals( UPARAM(ref) const FJsonLibraryValue& Target, const FJsonLibraryValue& Value ); + // Check if this value is valid. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Valid"), Category = "JSON Library|Value") + static bool JsonValue_IsValid( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Check if this value is a GUID. + UFUNCTION(BlueprintPure, meta = (DisplayName = "GUID"), Category = "JSON Library|Value|Engine") + static bool JsonValue_IsGuid( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Check if this value is a rotator. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Rotator"), Category = "JSON Library|Value|Engine") + static bool JsonValue_IsRotator( UPARAM(ref) const FJsonLibraryValue& Target ); + // Check if this value is a transform. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Transform"), Category = "JSON Library|Value|Engine") + static bool JsonValue_IsTransform( UPARAM(ref) const FJsonLibraryValue& Target ); + // Check if this value is a vector. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Vector"), Category = "JSON Library|Value|Engine") + static bool JsonValue_IsVector( UPARAM(ref) const FJsonLibraryValue& Target ); + + // Stringify this value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Stringify", AdvancedDisplay = "bCondensed"), Category = "JSON Library|Value") + static FString JsonValue_Stringify( UPARAM(ref) const FJsonLibraryValue& Target, bool bCondensed = true ); + + + // Check if this object equals another object. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Equals"), Category = "JSON Library|Object") + static bool JsonObject_Equals( UPARAM(ref) const FJsonLibraryObject& Target, const FJsonLibraryObject& Object ); + + // Get the number of properties in this object. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Count"), Category = "JSON Library|Object") + static int32 JsonObject_Count( UPARAM(ref) const FJsonLibraryObject& Target ); + // Clear the properties in this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Clear"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_Clear( UPARAM(ref) FJsonLibraryObject& Target ); + + // Check if this object has a property. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Has Property"), Category = "JSON Library|Object") + static bool JsonObject_HasKey( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Remove a property from this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Property"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_RemoveKey( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key ); + + // Add a JSON object to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_Add( UPARAM(ref) FJsonLibraryObject& Target, const FJsonLibraryObject& Object ); + + // Add a map of booleans to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Boolean Map"), Category = "JSON Library|Map") + static FJsonLibraryObject& JsonObject_AddBooleanMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, bool>& Map ); + // Add a map of floats to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Float Map"), Category = "JSON Library|Map") + static FJsonLibraryObject& JsonObject_AddFloatMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, float>& Map ); + // Add a map of integers to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Integer Map"), Category = "JSON Library|Map") + static FJsonLibraryObject& JsonObject_AddIntegerMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, int32>& Map ); + // Add a map of strings to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add String Map"), Category = "JSON Library|Map") + static FJsonLibraryObject& JsonObject_AddStringMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, FString>& Map ); + + // Add a map of date/times to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Date/Time Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject& JsonObject_AddDateTimeMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, FDateTime>& Map ); + // Add a map of GUIDs to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add GUID Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject& JsonObject_AddGuidMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, FGuid>& Map ); + + // Add a map of colors to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Color Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject& JsonObject_AddColorMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, FColor>& Map ); + // Add a map of linear colors to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Linear Color Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject& JsonObject_AddLinearColorMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, FLinearColor>& Map ); + + // Add a map of rotators to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Rotator Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject& JsonObject_AddRotatorMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, FRotator>& Map ); + // Add a map of transforms to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Transform Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject& JsonObject_AddTransformMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, FTransform>& Map ); + // Add a map of vectors to this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Vector Map"), Category = "JSON Library|Map|Engine") + static FJsonLibraryObject& JsonObject_AddVectorMap( UPARAM(ref) FJsonLibraryObject& Target, const TMap<FString, FVector>& Map ); + + // Get the keys of this object as an array of strings. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Keys"), Category = "JSON Library|Object") + static TArray<FString> JsonObject_GetKeys( UPARAM(ref) const FJsonLibraryObject& Target ); + // Get the values of this object as an array of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Values"), Category = "JSON Library|Object") + static TArray<FJsonLibraryValue> JsonObject_GetValues( UPARAM(ref) const FJsonLibraryObject& Target ); + + // Get a property as a boolean. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Boolean"), Category = "JSON Library|Object") + static bool JsonObject_GetBoolean( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a number. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Float"), Category = "JSON Library|Object") + static float JsonObject_GetFloat( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as an integer. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Integer"), Category = "JSON Library|Object") + static int32 JsonObject_GetInteger( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a string. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get String"), Category = "JSON Library|Object") + static FString JsonObject_GetString( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + + // Get a property as a date/time. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Date/Time"), Category = "JSON Library|Object|Engine") + static FDateTime JsonObject_GetDateTime( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a GUID. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get GUID"), Category = "JSON Library|Object|Engine") + static FGuid JsonObject_GetGuid( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + + // Get a property as a color. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Color"), Category = "JSON Library|Object|Engine") + static FColor JsonObject_GetColor( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a linear color. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Linear Color"), Category = "JSON Library|Object|Engine") + static FLinearColor JsonObject_GetLinearColor( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + + // Get a property as a rotator. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Rotator"), Category = "JSON Library|Object|Engine") + static FRotator JsonObject_GetRotator( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a transform. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Transform"), Category = "JSON Library|Object|Engine") + static FTransform JsonObject_GetTransform( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a vector. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Vector"), Category = "JSON Library|Object|Engine") + static FVector JsonObject_GetVector( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + + // Get a property as a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Value"), Category = "JSON Library|Object") + static FJsonLibraryValue JsonObject_GetValue( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Object"), Category = "JSON Library|Object") + static FJsonLibraryObject JsonObject_GetObject( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get List"), Category = "JSON Library|Object") + static FJsonLibraryList JsonObject_GetList( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + + // Get a property as an array of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Property To Array"), Category = "JSON Library|Array") + static TArray<FJsonLibraryValue> JsonObject_GetArray( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + // Get a property as a map of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Property To Map"), Category = "JSON Library|Map") + static TMap<FString, FJsonLibraryValue> JsonObject_GetMap( UPARAM(ref) const FJsonLibraryObject& Target, const FString& Key ); + + // Set a property as a boolean. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Boolean"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_SetBoolean( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, bool Value ); + // Set a property as a float. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Float"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_SetFloat( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, float Value ); + // Set a property as an integer. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Integer"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_SetInteger( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, int32 Value ); + // Set a property as a string. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set String"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_SetString( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FString& Value ); + + // Set a property as a date/time. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Date/Time"), Category = "JSON Library|Object|Engine") + static FJsonLibraryObject& JsonObject_SetDateTime( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FDateTime& Value ); + // Set a property as a GUID. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set GUID"), Category = "JSON Library|Object|Engine") + static FJsonLibraryObject& JsonObject_SetGuid( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FGuid& Value ); + + // Set a property as a color. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Color"), Category = "JSON Library|Object|Engine") + static FJsonLibraryObject& JsonObject_SetColor( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FColor& Value ); + // Set a property as a linear color. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Linear Color"), Category = "JSON Library|Object|Engine") + static FJsonLibraryObject& JsonObject_SetLinearColor( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FLinearColor& Value ); + + // Set a property as a rotator. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Rotator"), Category = "JSON Library|Object|Engine") + static FJsonLibraryObject& JsonObject_SetRotator( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FRotator& Value ); + // Set a property as a transform. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Transform"), Category = "JSON Library|Object|Engine") + static FJsonLibraryObject& JsonObject_SetTransform( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FTransform& Value ); + // Set a property as a vector. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Vector"), Category = "JSON Library|Object|Engine") + static FJsonLibraryObject& JsonObject_SetVector( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FVector& Value ); + + // Set a property as a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Value"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_SetValue( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FJsonLibraryValue& Value ); + // Set a property as a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Object"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_SetObject( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FJsonLibraryObject& Value ); + // Set a property as a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set List"), Category = "JSON Library|Object") + static FJsonLibraryObject& JsonObject_SetList( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const FJsonLibraryList& Value ); + + // Set a property as an array of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Property From Array"), Category = "JSON Library|Array") + static FJsonLibraryObject& JsonObject_SetArray( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const TArray<FJsonLibraryValue>& Value ); + // Set a property as a map of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Property From Map"), Category = "JSON Library|Map") + static FJsonLibraryObject& JsonObject_SetMap( UPARAM(ref) FJsonLibraryObject& Target, const FString& Key, const TMap<FString, FJsonLibraryValue>& Value ); + + // Check if this object is valid. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Valid"), Category = "JSON Library|Object") + static bool JsonObject_IsValid( UPARAM(ref) const FJsonLibraryObject& Target ); + // Check if this object is empty. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Empty"), Category = "JSON Library|Object") + static bool JsonObject_IsEmpty( UPARAM(ref) const FJsonLibraryObject& Target ); + + // Check if this object is a rotator. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Rotator"), Category = "JSON Library|Object|Engine") + static bool JsonObject_IsRotator( UPARAM(ref) const FJsonLibraryObject& Target ); + // Check if this object is a transform. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Transform"), Category = "JSON Library|Object|Engine") + static bool JsonObject_IsTransform( UPARAM(ref) const FJsonLibraryObject& Target ); + // Check if this object is a vector. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Vector"), Category = "JSON Library|Object|Engine") + static bool JsonObject_IsVector( UPARAM(ref) const FJsonLibraryObject& Target ); + + // Stringify this object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Stringify", AdvancedDisplay = "bCondensed"), Category = "JSON Library|Object") + static FString JsonObject_Stringify( UPARAM(ref) const FJsonLibraryObject& Target, bool bCondensed = true ); + + + // Check if this list equals another list. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Equals"), Category = "JSON Library|List") + static bool JsonList_Equals( UPARAM(ref) const FJsonLibraryList& Target, const FJsonLibraryList& List ); + + // Get the number of items in this list. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Count"), Category = "JSON Library|List") + static int32 JsonList_Count( UPARAM(ref) const FJsonLibraryList& Target ); + // Clear the items in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Clear"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_Clear( UPARAM(ref) FJsonLibraryList& Target ); + // Swap two items in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Swap"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_Swap( UPARAM(ref) FJsonLibraryList& Target, int32 IndexA, int32 IndexB ); + + // Append a JSON array to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_Append( UPARAM(ref) FJsonLibraryList& Target, const FJsonLibraryList& List ); + + // Append an array of booleans to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Boolean Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_AppendBooleanArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<bool>& Array ); + // Append an array of floats to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Float Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_AppendFloatArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<float>& Array ); + // Append an array of integers to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Integer Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_AppendIntegerArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<int32>& Array ); + // Append an array of strings to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append String Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_AppendStringArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FString>& Array ); + + // Append an array of date/times to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Date/Time Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_AppendDateTimeArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FDateTime>& Array ); + // Append an array of GUIDs to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append GUID Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_AppendGuidArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FGuid>& Array ); + + // Append an array of colors to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Color Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_AppendColorArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FColor>& Array ); + // Append an array of linear colors to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Linear Color Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_AppendLinearColorArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FLinearColor>& Array ); + + // Append an array of rotators to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Rotator Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_AppendRotatorArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FRotator>& Array ); + // Append an array of transforms to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Transform Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_AppendTransformArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FTransform>& Array ); + // Append an array of vectors to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Vector Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_AppendVectorArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FVector>& Array ); + + // Append an array of JSON objects to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Append Object Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_AppendObjectArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FJsonLibraryObject>& Array ); + + // Inject the items of a JSON array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_Inject( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FJsonLibraryList& List ); + + // Inject the items of a boolean array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Boolean Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_InjectBooleanArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<bool>& Array ); + // Inject the items of a float array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Float Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_InjectFloatArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<float>& Array ); + // Inject the items of an integer array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Integer Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_InjectIntegerArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<int32>& Array ); + // Inject the items of a string array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject String Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_InjectStringArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FString>& Array ); + + // Inject the items of a date/time array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Date/Time Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_InjectDateTimeArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FDateTime>& Array ); + // Inject the items of a GUID array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject GUID Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_InjectGuidArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FGuid>& Array ); + + // Inject the items of a color array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Color Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_InjectColorArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FColor>& Array ); + // Inject the items of a linear color array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Linear Color Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_InjectLinearColorArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FLinearColor>& Array ); + + // Inject the items of a rotator array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Rotator Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_InjectRotatorArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FRotator>& Array ); + // Inject the items of a transform array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Transform Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_InjectTransformArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FTransform>& Array ); + // Inject the items of a vector array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Vector Array"), Category = "JSON Library|Array|Engine") + static FJsonLibraryList& JsonList_InjectVectorArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FVector>& Array ); + + // Inject the items of an array of JSON objects into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Inject Object Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_InjectObjectArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FJsonLibraryObject>& Array ); + + // Add a boolean to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Boolean"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_AddBoolean( UPARAM(ref) FJsonLibraryList& Target, bool Value ); + // Add a number to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Float"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_AddFloat( UPARAM(ref) FJsonLibraryList& Target, float Value ); + // Add an integer to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Integer"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_AddInteger( UPARAM(ref) FJsonLibraryList& Target, int32 Value ); + // Add a string to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add String"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_AddString( UPARAM(ref) FJsonLibraryList& Target, const FString& Value ); + + // Add a date/time to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Date/Time"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_AddDateTime( UPARAM(ref) FJsonLibraryList& Target, const FDateTime& Value ); + // Add a GUID to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add GUID"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_AddGuid( UPARAM(ref) FJsonLibraryList& Target, const FGuid& Value ); + + // Add a color to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Color"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_AddColor( UPARAM(ref) FJsonLibraryList& Target, const FColor& Value ); + // Add a linear color to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Linear Color"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_AddLinearColor( UPARAM(ref) FJsonLibraryList& Target, const FLinearColor& Value ); + + // Add a rotator to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Rotator"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_AddRotator( UPARAM(ref) FJsonLibraryList& Target, const FRotator& Value ); + // Add a transform to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Transform"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_AddTransform( UPARAM(ref) FJsonLibraryList& Target, const FTransform& Value ); + // Add a vector to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Vector"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_AddVector( UPARAM(ref) FJsonLibraryList& Target, const FVector& Value ); + + // Add a JSON value to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Value"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_AddValue( UPARAM(ref) FJsonLibraryList& Target, const FJsonLibraryValue& Value ); + // Add a JSON object to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Object"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_AddObject( UPARAM(ref) FJsonLibraryList& Target, const FJsonLibraryObject& Value ); + // Add a JSON array to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add List"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_AddList( UPARAM(ref) FJsonLibraryList& Target, const FJsonLibraryList& Value ); + + // Add an array of JSON values to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Item From Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_AddArray( UPARAM(ref) FJsonLibraryList& Target, const TArray<FJsonLibraryValue>& Value ); + // Add a map of JSON values to this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Add Item From Map"), Category = "JSON Library|Map") + static FJsonLibraryList& JsonList_AddMap( UPARAM(ref) FJsonLibraryList& Target, const TMap<FString, FJsonLibraryValue>& Value ); + + // Insert a boolean into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Boolean"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_InsertBoolean( UPARAM(ref) FJsonLibraryList& Target, int32 Index, bool Value ); + // Insert a number into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Float"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_InsertFloat( UPARAM(ref) FJsonLibraryList& Target, int32 Index, float Value ); + // Insert an integer into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Integer"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_InsertInteger( UPARAM(ref) FJsonLibraryList& Target, int32 Index, int32 Value ); + // Insert a string into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert String"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_InsertString( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FString& Value ); + + // Insert a date/time into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Date/Time"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_InsertDateTime( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FDateTime& Value ); + // Insert a GUID into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert GUID"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_InsertGuid( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FGuid& Value ); + + // Insert a color into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Color"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_InsertColor( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FColor& Value ); + // Insert a linear color into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Linear Color"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_InsertLinearColor( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FLinearColor& Value ); + + // Insert a rotator into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Rotator"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_InsertRotator( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FRotator& Value ); + // Insert a transform into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Transform"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_InsertTransform( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FTransform& Value ); + // Insert a vector into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Vector"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_InsertVector( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FVector& Value ); + + // Insert a JSON value into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Value"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_InsertValue( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FJsonLibraryValue& Value ); + // Insert a JSON object into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Object"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_InsertObject( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FJsonLibraryObject& Value ); + // Insert a JSON array into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert List"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_InsertList( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FJsonLibraryList& Value ); + + // Insert an array of JSON values into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Item From Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_InsertArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FJsonLibraryValue>& Value ); + // Insert a map of JSON values into this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Insert Item From Map"), Category = "JSON Library|Map") + static FJsonLibraryList& JsonList_InsertMap( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TMap<FString, FJsonLibraryValue>& Value ); + + // Get an item as a boolean. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Boolean"), Category = "JSON Library|List") + static bool JsonList_GetBoolean( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as a number. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Float"), Category = "JSON Library|List") + static float JsonList_GetFloat( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as an integer. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Integer"), Category = "JSON Library|List") + static int32 JsonList_GetInteger( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as a string. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get String"), Category = "JSON Library|List") + static FString JsonList_GetString( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + + // Get an item as a date/time. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Date/Time"), Category = "JSON Library|List|Engine") + static FDateTime JsonList_GetDateTime( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as a GUID. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get GUID"), Category = "JSON Library|List|Engine") + static FGuid JsonList_GetGuid( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + + // Get an item as a color. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Color"), Category = "JSON Library|List|Engine") + static FColor JsonList_GetColor( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as a linear color. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Linear Color"), Category = "JSON Library|List|Engine") + static FLinearColor JsonList_GetLinearColor( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + + // Get an item as a rotator. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Rotator"), Category = "JSON Library|List|Engine") + static FRotator JsonList_GetRotator( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as a transform. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Transform"), Category = "JSON Library|List|Engine") + static FTransform JsonList_GetTransform( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as a vector. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Get Vector"), Category = "JSON Library|List|Engine") + static FVector JsonList_GetVector( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + + // Get an item as a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Value"), Category = "JSON Library|List") + static FJsonLibraryValue JsonList_GetValue( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get Object"), Category = "JSON Library|List") + static FJsonLibraryObject JsonList_GetObject( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Get an item as a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Get List"), Category = "JSON Library|List") + static FJsonLibraryList JsonList_GetList( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + + // Copy an item to an array of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Item To Array"), Category = "JSON Library|Array") + static TArray<FJsonLibraryValue> JsonList_GetArray( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + // Copy an item to a map of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Copy Item To Map"), Category = "JSON Library|Map") + static TMap<FString, FJsonLibraryValue> JsonList_GetMap( UPARAM(ref) const FJsonLibraryList& Target, int32 Index ); + + // Set an item as a boolean. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Boolean"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_SetBoolean( UPARAM(ref) FJsonLibraryList& Target, int32 Index, bool Value ); + // Set an item as a number. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Float"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_SetFloat( UPARAM(ref) FJsonLibraryList& Target, int32 Index, float Value ); + // Set an item as an integer. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Integer"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_SetInteger( UPARAM(ref) FJsonLibraryList& Target, int32 Index, int32 Value ); + // Set an item as a string. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set String"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_SetString( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FString& Value ); + + // Set an item as a date/time. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Date/Time"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_SetDateTime( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FDateTime& Value ); + // Set an item as a GUID. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set GUID"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_SetGuid( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FGuid& Value ); + + // Set an item as a color. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Color"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_SetColor( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FColor& Value ); + // Set an item as a linear color. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Linear Color"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_SetLinearColor( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FLinearColor& Value ); + + // Set an item as a rotator. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Rotator"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_SetRotator( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FRotator& Value ); + // Set an item as a transform. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Transform"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_SetTransform( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FTransform& Value ); + // Set an item as a vector. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Vector"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_SetVector( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FVector& Value ); + + // Set an item as a JSON value. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Value"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_SetValue( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FJsonLibraryValue& Value ); + // Set an item as a JSON object. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Object"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_SetObject( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FJsonLibraryObject& Value ); + // Set an item as a JSON array. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set List"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_SetList( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const FJsonLibraryList& Value ); + + // Set an item as an array of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Item From Array"), Category = "JSON Library|Array") + static FJsonLibraryList& JsonList_SetArray( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TArray<FJsonLibraryValue>& Value ); + // Set an item as a map of JSON values. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Set Item From Map"), Category = "JSON Library|Map") + static FJsonLibraryList& JsonList_SetMap( UPARAM(ref) FJsonLibraryList& Target, int32 Index, const TMap<FString, FJsonLibraryValue>& Value ); + + // Remove an item from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_Remove( UPARAM(ref) FJsonLibraryList& Target, int32 Index ); + + // Remove a boolean from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Boolean"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_RemoveBoolean( UPARAM(ref) FJsonLibraryList& Target, bool Value ); + // Remove a float from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Float"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_RemoveFloat( UPARAM(ref) FJsonLibraryList& Target, float Value ); + // Remove an integer from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Integer"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_RemoveInteger( UPARAM(ref) FJsonLibraryList& Target, int32 Value ); + // Remove a string from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove String"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_RemoveString( UPARAM(ref) FJsonLibraryList& Target, const FString& Value ); + + // Remove a date/time from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Date/Time"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_RemoveDateTime( UPARAM(ref) FJsonLibraryList& Target, const FDateTime& Value ); + // Remove a GUID from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove GUID"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_RemoveGuid( UPARAM(ref) FJsonLibraryList& Target, const FGuid& Value ); + + // Remove a color from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Color"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_RemoveColor( UPARAM(ref) FJsonLibraryList& Target, const FColor& Value ); + // Remove a linear color from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Linear Color"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_RemoveLinearColor( UPARAM(ref) FJsonLibraryList& Target, const FLinearColor& Value ); + + // Remove a rotator from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Rotator"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_RemoveRotator( UPARAM(ref) FJsonLibraryList& Target, const FRotator& Value ); + // Remove a transform from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Transform"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_RemoveTransform( UPARAM(ref) FJsonLibraryList& Target, const FTransform& Value ); + // Remove a vector from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Vector"), Category = "JSON Library|List|Engine") + static FJsonLibraryList& JsonList_RemoveVector( UPARAM(ref) FJsonLibraryList& Target, const FVector& Value ); + + // Remove a JSON value from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Value"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_RemoveValue( UPARAM(ref) FJsonLibraryList& Target, const FJsonLibraryValue& Value ); + // Remove a JSON object from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove Object"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_RemoveObject( UPARAM(ref) FJsonLibraryList& Target, const FJsonLibraryObject& Value ); + // Remove a JSON array from this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Remove List"), Category = "JSON Library|List") + static FJsonLibraryList& JsonList_RemoveList( UPARAM(ref) FJsonLibraryList& Target, const FJsonLibraryList& Value ); + + // Find a boolean in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Boolean"), Category = "JSON Library|List") + static int32 JsonList_FindBoolean( UPARAM(ref) const FJsonLibraryList& Target, bool Value, int32 Index = 0 ); + // Find a float in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Float"), Category = "JSON Library|List") + static int32 JsonList_FindFloat( UPARAM(ref) const FJsonLibraryList& Target, float Value, int32 Index = 0 ); + // Find an integer in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Integer"), Category = "JSON Library|List") + static int32 JsonList_FindInteger( UPARAM(ref) const FJsonLibraryList& Target, int32 Value, int32 Index = 0 ); + // Find a string in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find String"), Category = "JSON Library|List") + static int32 JsonList_FindString( UPARAM(ref) const FJsonLibraryList& Target, const FString& Value, int32 Index = 0 ); + + // Find a date/time in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Date/Time"), Category = "JSON Library|List|Engine") + static int32 JsonList_FindDateTime( UPARAM(ref) const FJsonLibraryList& Target, const FDateTime& Value, int32 Index = 0 ); + // Find a GUID in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find GUID"), Category = "JSON Library|List|Engine") + static int32 JsonList_FindGuid( UPARAM(ref) const FJsonLibraryList& Target, const FGuid& Value, int32 Index = 0 ); + + // Find a color in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Color"), Category = "JSON Library|List|Engine") + static int32 JsonList_FindColor( UPARAM(ref) const FJsonLibraryList& Target, const FColor& Value, int32 Index = 0 ); + // Find a linear color in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Linear Color"), Category = "JSON Library|List|Engine") + static int32 JsonList_FindLinearColor( UPARAM(ref) const FJsonLibraryList& Target, const FLinearColor& Value, int32 Index = 0 ); + + // Find a rotator in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Rotator"), Category = "JSON Library|List|Engine") + static int32 JsonList_FindRotator( UPARAM(ref) const FJsonLibraryList& Target, const FRotator& Value, int32 Index = 0 ); + // Find a transform in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Transform"), Category = "JSON Library|List|Engine") + static int32 JsonList_FindTransform( UPARAM(ref) const FJsonLibraryList& Target, const FTransform& Value, int32 Index = 0 ); + // Find a vector in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Vector"), Category = "JSON Library|List|Engine") + static int32 JsonList_FindVector( UPARAM(ref) const FJsonLibraryList& Target, const FVector& Value, int32 Index = 0 ); + + // Find a JSON value in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Value"), Category = "JSON Library|List") + static int32 JsonList_FindValue( UPARAM(ref) const FJsonLibraryList& Target, const FJsonLibraryValue& Value, int32 Index = 0 ); + // Find a JSON object in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find Object"), Category = "JSON Library|List") + static int32 JsonList_FindObject( UPARAM(ref) const FJsonLibraryList& Target, const FJsonLibraryObject& Value, int32 Index = 0 ); + // Find a JSON array in this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Find List"), Category = "JSON Library|List") + static int32 JsonList_FindList( UPARAM(ref) const FJsonLibraryList& Target, const FJsonLibraryList& Value, int32 Index = 0 ); + + // Check if this list is valid. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Valid"), Category = "JSON Library|List") + static bool JsonList_IsValid( UPARAM(ref) const FJsonLibraryList& Target ); + // Check if this list is empty. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Empty"), Category = "JSON Library|List") + static bool JsonList_IsEmpty( UPARAM(ref) const FJsonLibraryList& Target ); + + // Stringify this list. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Stringify", AdvancedDisplay = "bCondensed"), Category = "JSON Library|List") + static FString JsonList_Stringify( UPARAM(ref) const FJsonLibraryList& Target, bool bCondensed = true ); + +public: + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Strip Comments/Commas (JSON)", AdvancedDisplay = "bComments,bTrailingCommas"), Category = "JSON Library|Helpers") + static FString StripCommentsOrCommas( const FString& Text, bool bComments = true, bool bTrailingCommas = true ); +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryList.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryList.h new file mode 100644 index 0000000000000000000000000000000000000000..d344fa0686e848f5026711ac24a2edd1fb1636f9 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryList.h @@ -0,0 +1,446 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "Dom/JsonValue.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonTypes.h" +#include "JsonLibraryEnums.h" +#include "JsonLibraryValue.h" +#include "JsonLibraryList.generated.h" + +typedef struct FJsonLibraryObject FJsonLibraryObject; + +DECLARE_DYNAMIC_DELEGATE_FourParams( FJsonLibraryListNotify, const FJsonLibraryValue&, List, EJsonLibraryNotifyAction, Action, int32, Index, const FJsonLibraryValue&, Value ); + +USTRUCT(BlueprintType, meta = (DisplayName = "JSON List")) +struct JSONLIBRARY_API FJsonLibraryList +{ + friend struct FJsonLibraryObject; + friend struct FJsonLibraryValue; + + GENERATED_USTRUCT_BODY() + +protected: + + FJsonLibraryList( const TSharedPtr<FJsonValue>& Value ); + FJsonLibraryList( const TSharedPtr<FJsonValueArray>& Value ); + +public: + + FJsonLibraryList(); + FJsonLibraryList( const FJsonLibraryListNotify& Notify ); + + FJsonLibraryList( const TArray<FJsonLibraryValue>& Value ); + FJsonLibraryList( const TArray<bool>& Value ); + FJsonLibraryList( const TArray<float>& Value ); + FJsonLibraryList( const TArray<double>& Value ); + FJsonLibraryList( const TArray<int32>& Value ); + FJsonLibraryList( const TArray<FString>& Value ); + + FJsonLibraryList( const TArray<FDateTime>& Value ); + FJsonLibraryList( const TArray<FGuid>& Value ); + + FJsonLibraryList( const TArray<FColor>& Value ); + FJsonLibraryList( const TArray<FLinearColor>& Value ); + + FJsonLibraryList( const TArray<FRotator>& Value ); + FJsonLibraryList( const TArray<FTransform>& Value ); + FJsonLibraryList( const TArray<FVector>& Value ); + + FJsonLibraryList( const TArray<FJsonLibraryObject>& Value ); + + // Check if this list equals another JSON array. + bool Equals( const FJsonLibraryList& List ) const; + + // Get the number of items in this list. + int32 Count() const; + // Clear the items in this list. + void Clear(); + // Swap two items in this list. + void Swap( int32 IndexA, int32 IndexB ); + + // Append a JSON array to this list. + void Append( const FJsonLibraryList& List ); + + // Append an array of booleans to this list. + void AppendBooleanArray( const TArray<bool>& Array ); + // Append an array of floats to this list. + void AppendFloatArray( const TArray<float>& Array ); + // Append an array of integers to this list. + void AppendIntegerArray( const TArray<int32>& Array ); + // Append an array of numbers to this list. + void AppendNumberArray( const TArray<double>& Array ); + // Append an array of strings to this list. + void AppendStringArray( const TArray<FString>& Array ); + + // Append an array of JSON objects to this list. + void AppendObjectArray( const TArray<FJsonLibraryObject>& Array ); + + // Append an array of date/times to this list. + void AppendDateTimeArray( const TArray<FDateTime>& Array ); + // Append an array of GUIDs to this list. + void AppendGuidArray( const TArray<FGuid>& Array ); + + // Append an array of colors to this list. + void AppendColorArray( const TArray<FColor>& Array ); + // Append an array of linear colors to this list. + void AppendLinearColorArray( const TArray<FLinearColor>& Array ); + + // Append an array of rotators to this list. + void AppendRotatorArray( const TArray<FRotator>& Array ); + // Append an array of transforms to this list. + void AppendTransformArray( const TArray<FTransform>& Array ); + // Append an array of vectors to this list. + void AppendVectorArray( const TArray<FVector>& Array ); + + // Inject the items of a JSON array into this list. + void Inject( int32 Index, const FJsonLibraryList& List ); + + // Inject the items of a boolean array into this list. + void InjectBooleanArray( int32 Index, const TArray<bool>& Array ); + // Inject the items of a float array into this list. + void InjectFloatArray( int32 Index, const TArray<float>& Array ); + // Inject the items of an integer array into this list. + void InjectIntegerArray( int32 Index, const TArray<int32>& Array ); + // Inject the items of a number array into this list. + void InjectNumberArray( int32 Index, const TArray<double>& Array ); + // Inject the items of a string array into this list. + void InjectStringArray( int32 Index, const TArray<FString>& Array ); + + // Inject the items of an array of JSON objects into this list. + void InjectObjectArray( int32 Index, const TArray<FJsonLibraryObject>& Array ); + + // Inject the items of a date/time array into this list. + void InjectDateTimeArray( int32 Index, const TArray<FDateTime>& Array ); + // Inject the items of a GUID array into this list. + void InjectGuidArray( int32 Index, const TArray<FGuid>& Array ); + + // Inject the items of a color array into this list. + void InjectColorArray( int32 Index, const TArray<FColor>& Array ); + // Inject the items of a linear color array into this list. + void InjectLinearColorArray( int32 Index, const TArray<FLinearColor>& Array ); + + // Inject the items of a rotator array into this list. + void InjectRotatorArray( int32 Index, const TArray<FRotator>& Array ); + // Inject the items of a transform array into this list. + void InjectTransformArray( int32 Index, const TArray<FTransform>& Array ); + // Inject the items of a vector array into this list. + void InjectVectorArray( int32 Index, const TArray<FVector>& Array ); + + // Add a boolean to this list. + void AddBoolean( bool Value ); + // Add a float to this list. + void AddFloat( float Value ); + // Add an integer to this list. + void AddInteger( int32 Value ); + // Add a number to this list. + void AddNumber( double Value ); + // Add a string to this list. + void AddString( const FString& Value ); + + // Add a date/time to this list. + void AddDateTime( const FDateTime& Value ); + // Add a GUID to this list. + void AddGuid( const FGuid& Value ); + + // Add a color to this list. + void AddColor( const FColor& Value ); + // Add a linear color to this list. + void AddLinearColor( const FLinearColor& Value ); + + // Add a rotator to this list. + void AddRotator( const FRotator& Value ); + // Add a transform to this list. + void AddTransform( const FTransform& Value ); + // Add a vector to this list. + void AddVector( const FVector& Value ); + + // Add a JSON value to this list. + void AddValue( const FJsonLibraryValue& Value ); + // Add a JSON object to this list. + void AddObject( const FJsonLibraryObject& Value ); + // Add a JSON array to this list. + void AddList( const FJsonLibraryList& Value ); + + // Add an array of JSON values to this list. + void AddArray( const TArray<FJsonLibraryValue>& Value ); + // Add a map of JSON values to this list. + void AddMap( const TMap<FString, FJsonLibraryValue>& Value ); + + // Insert a boolean into this list. + void InsertBoolean( int32 Index, bool Value ); + // Insert a float into this list. + void InsertFloat( int32 Index, float Value ); + // Insert an integer into this list. + void InsertInteger( int32 Index, int32 Value ); + // Insert a number into this list. + void InsertNumber( int32 Index, double Value ); + // Insert a string into this list. + void InsertString( int32 Index, const FString& Value ); + + // Insert a date/time into this list. + void InsertDateTime( int32 Index, const FDateTime& Value ); + // Insert a GUID into this list. + void InsertGuid( int32 Index, const FGuid& Value ); + + // Insert a color into this list. + void InsertColor( int32 Index, const FColor& Value ); + // Insert a linear color into this list. + void InsertLinearColor( int32 Index, const FLinearColor& Value ); + + // Insert a rotator into this list. + void InsertRotator( int32 Index, const FRotator& Value ); + // Insert a transform into this list. + void InsertTransform( int32 Index, const FTransform& Value ); + // Insert a vector into this list. + void InsertVector( int32 Index, const FVector& Value ); + + // Insert a JSON value into this list. + void InsertValue( int32 Index, const FJsonLibraryValue& Value ); + // Insert a JSON object into this list. + void InsertObject( int32 Index, const FJsonLibraryObject& Value ); + // Insert a JSON array into this list. + void InsertList( int32 Index, const FJsonLibraryList& Value ); + + // Insert an array of JSON values into this list. + void InsertArray( int32 Index, const TArray<FJsonLibraryValue>& Value ); + // Insert a map of JSON values into this list. + void InsertMap( int32 Index, const TMap<FString, FJsonLibraryValue>& Value ); + + // Get an item as a boolean. + bool GetBoolean( int32 Index ) const; + // Get an item as a float. + float GetFloat( int32 Index ) const; + // Get an item as an integer. + int32 GetInteger( int32 Index ) const; + // Get an item as a number. + double GetNumber( int32 Index ) const; + // Get an item as a string. + FString GetString( int32 Index ) const; + + // Get an item as a date/time. + FDateTime GetDateTime( int32 Index ) const; + // Get an item as a GUID. + FGuid GetGuid( int32 Index ) const; + + // Get an item as a color. + FColor GetColor( int32 Index ) const; + // Get an item as a linear color. + FLinearColor GetLinearColor( int32 Index ) const; + + // Get an item as a rotator. + FRotator GetRotator( int32 Index ) const; + // Get an item as a transform. + FTransform GetTransform( int32 Index ) const; + // Get an item as a vector. + FVector GetVector( int32 Index ) const; + + // Get an item as a JSON value. + FJsonLibraryValue GetValue( int32 Index ) const; + // Get an item as a JSON object. + FJsonLibraryObject GetObject( int32 Index ) const; + // Get an item as a JSON array. + FJsonLibraryList GetList( int32 Index ) const; + + // Copy an item to an array of JSON values. + TArray<FJsonLibraryValue> GetArray( int32 Index ) const; + // Copy an item to a map of JSON values. + TMap<FString, FJsonLibraryValue> GetMap( int32 Index ) const; + + // Set an item as a boolean. + void SetBoolean( int32 Index, bool Value ); + // Set an item as a float. + void SetFloat( int32 Index, float Value ); + // Set an item as an integer. + void SetInteger( int32 Index, int32 Value ); + // Set an item as a number. + void SetNumber( int32 Index, double Value ); + // Set an item as a string. + void SetString( int32 Index, const FString& Value ); + + // Set an item as a date/time. + void SetDateTime( int32 Index, const FDateTime& Value ); + // Set an item as a GUID. + void SetGuid( int32 Index, const FGuid& Value ); + + // Set an item as a color. + void SetColor( int32 Index, const FColor& Value ); + // Set an item as a linear color. + void SetLinearColor( int32 Index, const FLinearColor& Value ); + + // Set an item as a rotator. + void SetRotator( int32 Index, const FRotator& Value ); + // Set an item as a transform. + void SetTransform( int32 Index, const FTransform& Value ); + // Set an item as a vector. + void SetVector( int32 Index, const FVector& Value ); + + // Set an item as a JSON value. + void SetValue( int32 Index, const FJsonLibraryValue& Value ); + // Set an item as a JSON object. + void SetObject( int32 Index, const FJsonLibraryObject& Value ); + // Set an item as a JSON array. + void SetList( int32 Index, const FJsonLibraryList& Value ); + + // Set an item as an array of JSON values. + void SetArray( int32 Index, const TArray<FJsonLibraryValue>& Value ); + // Set an item as a map of JSON values. + void SetMap( int32 Index, const TMap<FString, FJsonLibraryValue>& Value ); + + // Remove an item from this list. + void Remove( int32 Index ); + + // Remove a boolean from this list. + void RemoveBoolean( bool Value ); + // Remove a float from this list. + void RemoveFloat( float Value ); + // Remove an integer from this list. + void RemoveInteger( int32 Value ); + // Remove a number from this list. + void RemoveNumber( double Value ); + // Remove a string from this list. + void RemoveString( const FString& Value ); + + // Remove a date/time from this list. + void RemoveDateTime( const FDateTime& Value ); + // Remove a GUID from this list. + void RemoveGuid( const FGuid& Value ); + + // Remove a color from this list. + void RemoveColor( const FColor& Value ); + // Remove a linear color from this list. + void RemoveLinearColor( const FLinearColor& Value ); + + // Remove a rotator from this list. + void RemoveRotator( const FRotator& Value ); + // Remove a transform from this list. + void RemoveTransform( const FTransform& Value ); + // Remove a vector from this list. + void RemoveVector( const FVector& Value ); + + // Remove a JSON value from this list. + void RemoveValue( const FJsonLibraryValue& Value ); + // Remove a JSON object from this list. + void RemoveObject( const FJsonLibraryObject& Value ); + // Remove a JSON array from this list. + void RemoveList( const FJsonLibraryList& Value ); + + // Find a boolean in this list. + int32 FindBoolean( bool Value, int32 Index = 0 ) const; + // Find a float in this list. + int32 FindFloat( float Value, int32 Index = 0 ) const; + // Find an integer in this list. + int32 FindInteger( int32 Value, int32 Index = 0 ) const; + // Find a number in this list. + int32 FindNumber( double Value, int32 Index = 0 ) const; + // Find a string in this list. + int32 FindString( const FString& Value, int32 Index = 0 ) const; + + // Find a date/time in this list. + int32 FindDateTime( const FDateTime& Value, int32 Index = 0 ) const; + // Find a GUID in this list. + int32 FindGuid( const FGuid& Value, int32 Index = 0 ) const; + + // Find a color in this list. + int32 FindColor( const FColor& Value, int32 Index = 0 ) const; + // Find a linear color in this list. + int32 FindLinearColor( const FLinearColor& Value, int32 Index = 0 ) const; + + // Find a rotator in this list. + int32 FindRotator( const FRotator& Value, int32 Index = 0 ) const; + // Find a transform in this list. + int32 FindTransform( const FTransform& Value, int32 Index = 0 ) const; + // Find a vector in this list. + int32 FindVector( const FVector& Value, int32 Index = 0 ) const; + + // Find a JSON value in this list. + int32 FindValue( const FJsonLibraryValue& Value, int32 Index = 0 ) const; + // Find a JSON object in this list. + int32 FindObject( const FJsonLibraryObject& Value, int32 Index = 0 ) const; + // Find a JSON array in this list. + int32 FindList( const FJsonLibraryList& Value, int32 Index = 0 ) const; + +protected: + + TSharedPtr<FJsonValueArray> JsonArray; + + const TArray<TSharedPtr<FJsonValue>>* GetJsonArray() const; + TArray<TSharedPtr<FJsonValue>>* SetJsonArray(); + + bool TryParse( const FString& Text, bool bStripComments = false, bool bStripTrailingCommas = false ); + bool TryStringify( FString& Text, bool bCondensed = true ) const; + +private: + + FJsonLibraryListNotify OnNotify; + + bool bNotifyHasIndex; + TSharedPtr<FJsonValue> NotifyValue; + + void NotifyAdd( int32 Index, const FJsonLibraryValue& Value ); + void NotifyChange( int32 Index, const FJsonLibraryValue& Value ); + bool NotifyCheck(); + bool NotifyCheck( int32 Index ); + void NotifyClear(); + void NotifyParse(); + void NotifyRemove( int32 Index ); + +public: + + // Check if this list is valid. + bool IsValid() const; + // Check if this list is empty. + bool IsEmpty() const; + + // Parse a JSON string. + static FJsonLibraryList Parse( const FString& Text ); + // Parse a JSON string. + static FJsonLibraryList Parse( const FString& Text, const FJsonLibraryListNotify& Notify ); + + // Parse a relaxed JSON string. + static FJsonLibraryList ParseRelaxed( const FString& Text, bool bStripComments = true, bool bStripTrailingCommas = true ); + + // Stringify this list as a JSON string. + FString Stringify( bool bCondensed = true ) const; + + // Copy this list to an array of JSON values. + TArray<FJsonLibraryValue> ToArray() const; + + // Copy this list to an array of booleans. + TArray<bool> ToBooleanArray() const; + // Copy this list to an array of floats. + TArray<float> ToFloatArray() const; + // Copy this list to an array of integers. + TArray<int32> ToIntegerArray() const; + // Copy this list to an array of numbers. + TArray<double> ToNumberArray() const; + // Copy this list to an array of strings. + TArray<FString> ToStringArray() const; + + // Copy this list to an array of date/times. + TArray<FDateTime> ToDateTimeArray() const; + // Copy this list to an array of GUIDs. + TArray<FGuid> ToGuidArray() const; + + // Copy this list to an array of colors. + TArray<FColor> ToColorArray() const; + // Copy this list to an array of linear colors. + TArray<FLinearColor> ToLinearColorArray() const; + + // Copy this list to an array of rotators. + TArray<FRotator> ToRotatorArray() const; + // Copy this list to an array of transforms. + TArray<FTransform> ToTransformArray() const; + // Copy this list to an array of vectors. + TArray<FVector> ToVectorArray() const; + + // Copy this list to an array of JSON objects. + TArray<FJsonLibraryObject> ToObjectArray() const; + + bool operator==( const FJsonLibraryList& List ) const; + bool operator!=( const FJsonLibraryList& List ) const; + + bool operator==( const FJsonLibraryValue& Value ) const; + bool operator!=( const FJsonLibraryValue& Value ) const; +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryModule.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryModule.h new file mode 100644 index 0000000000000000000000000000000000000000..b8fde646e37573052b4ad5a8a7dc6d4a4d83e9ba --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryModule.h @@ -0,0 +1,18 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "Modules/ModuleManager.h" + +class IJsonLibraryModule : public IModuleInterface +{ +public: + + static inline IJsonLibraryModule& Get() + { + return FModuleManager::LoadModuleChecked<IJsonLibraryModule>( "JsonLibrary" ); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "JsonLibrary" ); + } +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryObject.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryObject.h new file mode 100644 index 0000000000000000000000000000000000000000..8798eb6a560242725b31159fad4d25ba102dd6d6 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryObject.h @@ -0,0 +1,317 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "Dom/JsonValue.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonTypes.h" +#include "UObject/StructOnScope.h" +#include "JsonLibraryEnums.h" +#include "JsonLibraryValue.h" +#include "JsonLibraryObject.generated.h" + +typedef struct FJsonLibraryList FJsonLibraryList; + +DECLARE_DYNAMIC_DELEGATE_FourParams( FJsonLibraryObjectNotify, const FJsonLibraryValue&, Object, EJsonLibraryNotifyAction, Action, const FString&, Key, const FJsonLibraryValue&, Value ); + +USTRUCT(BlueprintType, meta = (DisplayName = "JSON Object")) +struct JSONLIBRARY_API FJsonLibraryObject +{ + friend struct FJsonLibraryList; + friend struct FJsonLibraryValue; + + friend class UJsonLibraryBlueprintHelpers; + + GENERATED_USTRUCT_BODY() + +protected: + + FJsonLibraryObject( const TSharedPtr<FJsonValue>& Value ); + FJsonLibraryObject( const TSharedPtr<FJsonValueObject>& Value ); + + FJsonLibraryObject( const UStruct* StructType, const void* StructPtr ); + +public: + + FJsonLibraryObject( const TSharedPtr<FStructOnScope>& StructData ); + + template<typename StructType> + FJsonLibraryObject( const StructType& Struct ) + : FJsonLibraryObject( StructType::StaticStruct(), &Struct ) + { + } + + FJsonLibraryObject(); + FJsonLibraryObject( const FJsonLibraryObjectNotify& Notify ); + + FJsonLibraryObject( const FLinearColor& Value ); + + FJsonLibraryObject( const FRotator& Value ); + FJsonLibraryObject( const FTransform& Value ); + FJsonLibraryObject( const FVector& Value ); + + FJsonLibraryObject( const TMap<FString, FJsonLibraryValue>& Value ); + FJsonLibraryObject( const TMap<FString, bool>& Value ); + FJsonLibraryObject( const TMap<FString, float>& Value ); + FJsonLibraryObject( const TMap<FString, double>& Value ); + FJsonLibraryObject( const TMap<FString, int32>& Value ); + FJsonLibraryObject( const TMap<FString, FString>& Value ); + + FJsonLibraryObject( const TMap<FString, FDateTime>& Value ); + FJsonLibraryObject( const TMap<FString, FGuid>& Value ); + + FJsonLibraryObject( const TMap<FString, FColor>& Value ); + FJsonLibraryObject( const TMap<FString, FLinearColor>& Value ); + + FJsonLibraryObject( const TMap<FString, FRotator>& Value ); + FJsonLibraryObject( const TMap<FString, FTransform>& Value ); + FJsonLibraryObject( const TMap<FString, FVector>& Value ); + + // Check if this object equals another JSON object. + bool Equals( const FJsonLibraryObject& Object ) const; + + // Get the number of properties in this object. + int32 Count() const; + // Clear the properties in this object. + void Clear(); + + // Check if this object has a property. + bool HasKey( const FString& Key ) const; + // Remove a property from this object. + void RemoveKey( const FString& Key ); + + // Add a JSON object to this object. + void Add( const FJsonLibraryObject& Object ); + + // Add a map of booleans to this object. + void AddBooleanMap( const TMap<FString, bool>& Map ); + // Add a map of floats to this object. + void AddFloatMap( const TMap<FString, float>& Map ); + // Add a map of integers to this object. + void AddIntegerMap( const TMap<FString, int32>& Map ); + // Add a map of numbers to this object. + void AddNumberMap( const TMap<FString, double>& Map ); + // Add a map of strings to this object. + void AddStringMap( const TMap<FString, FString>& Map ); + + // Add a map of date/times to this object. + void AddDateTimeMap( const TMap<FString, FDateTime>& Map ); + // Add a map of GUIDs to this object. + void AddGuidMap( const TMap<FString, FGuid>& Map ); + + // Add a map of colors to this object. + void AddColorMap( const TMap<FString, FColor>& Map ); + // Add a map of linear colors to this object. + void AddLinearColorMap( const TMap<FString, FLinearColor>& Map ); + + // Add a map of rotators to this object. + void AddRotatorMap( const TMap<FString, FRotator>& Map ); + // Add a map of transforms to this object. + void AddTransformMap( const TMap<FString, FTransform>& Map ); + // Add a map of vectors to this object. + void AddVectorMap( const TMap<FString, FVector>& Map ); + + // Get the keys of this object as an array of strings. + TArray<FString> GetKeys() const; + // Get the values of this object as an array of JSON values. + TArray<FJsonLibraryValue> GetValues() const; + + // Get a property as a boolean. + bool GetBoolean( const FString& Key ) const; + // Get a property as a float. + float GetFloat( const FString& Key ) const; + // Get a property as an integer. + int32 GetInteger( const FString& Key ) const; + // Get a property as a number. + double GetNumber( const FString& Key ) const; + // Get a property as a string. + FString GetString( const FString& Key ) const; + + // Get a property as a date/time. + FDateTime GetDateTime( const FString& Key ) const; + // Get a property as a GUID. + FGuid GetGuid( const FString& Key ) const; + + // Get a property as a color. + FColor GetColor( const FString& Key ) const; + // Get a property as a linear color. + FLinearColor GetLinearColor( const FString& Key ) const; + + // Get a property as a rotator. + FRotator GetRotator( const FString& Key ) const; + // Get a property as a transform. + FTransform GetTransform( const FString& Key ) const; + // Get a property as a vector. + FVector GetVector( const FString& Key ) const; + + // Get a property as a JSON value. + FJsonLibraryValue GetValue( const FString& Key ) const; + // Get a property as a JSON object. + FJsonLibraryObject GetObject( const FString& Key ) const; + // Get a property as a JSON array. + FJsonLibraryList GetList( const FString& Key ) const; + + // Get a property as an array of JSON values. + TArray<FJsonLibraryValue> GetArray( const FString& Key ) const; + // Get a property as a map of JSON values. + TMap<FString, FJsonLibraryValue> GetMap( const FString& Key ) const; + + // Set a property as a boolean. + void SetBoolean( const FString& Key, bool Value ); + // Set a property as a float. + void SetFloat( const FString& Key, float Value ); + // Set a property as an integer. + void SetInteger( const FString& Key, int32 Value ); + // Set a property as a number. + void SetNumber( const FString& Key, double Value ); + // Set a property as a string. + void SetString( const FString& Key, const FString& Value ); + + // Set a property as a date/time. + void SetDateTime( const FString& Key, const FDateTime& Value ); + // Set a property as a GUID. + void SetGuid( const FString& Key, const FGuid& Value ); + + // Set a property as a color. + void SetColor( const FString& Key, const FColor& Value ); + // Set a property as a linear color. + void SetLinearColor( const FString& Key, const FLinearColor& Value ); + + // Set a property as a rotator. + void SetRotator( const FString& Key, const FRotator& Value ); + // Set a property as a transform. + void SetTransform( const FString& Key, const FTransform& Value ); + // Set a property as a vector. + void SetVector( const FString& Key, const FVector& Value ); + + // Set a property as a JSON value. + void SetValue( const FString& Key, const FJsonLibraryValue& Value ); + // Set a property as a JSON object. + void SetObject( const FString& Key, const FJsonLibraryObject& Value ); + // Set a property as a JSON array. + void SetList( const FString& Key, const FJsonLibraryList& Value ); + + // Set a property as an array of JSON values. + void SetArray( const FString& Key, const TArray<FJsonLibraryValue>& Value ); + // Set a property as a map of JSON values. + void SetMap( const FString& Key, const TMap<FString, FJsonLibraryValue>& Value ); + +protected: + + TSharedPtr<FJsonValueObject> JsonObject; + + const TSharedPtr<FJsonObject> GetJsonObject() const; + TSharedPtr<FJsonObject> SetJsonObject(); + + bool TryParse( const FString& Text, bool bStripComments = false, bool bStripTrailingCommas = false ); + bool TryStringify( FString& Text, bool bCondensed = true ) const; + +private: + + FJsonLibraryObjectNotify OnNotify; + + bool bNotifyHasKey; + TSharedPtr<FJsonValue> NotifyValue; + + void NotifyAddOrChange( const FString& Key, const FJsonLibraryValue& Value ); + bool NotifyCheck(); + bool NotifyCheck( const FString& Key ); + void NotifyClear(); + void NotifyParse(); + void NotifyRemove( const FString& Key ); + +public: + + // Check if this object is valid. + bool IsValid() const; + // Check if this object is empty. + bool IsEmpty() const; + + // Check if this object is a linear color. + bool IsLinearColor() const; + + // Check if this object is a rotator. + bool IsRotator() const; + // Check if this object is a transform. + bool IsTransform() const; + // Check if this object is a vector. + bool IsVector() const; + + // Parse a JSON string. + static FJsonLibraryObject Parse( const FString& Text ); + // Parse a JSON string. + static FJsonLibraryObject Parse( const FString& Text, const FJsonLibraryObjectNotify& Notify ); + + // Parse a relaxed JSON string. + static FJsonLibraryObject ParseRelaxed( const FString& Text, bool bStripComments = true, bool bStripTrailingCommas = true ); + + // Stringify this object as a JSON string. + FString Stringify( bool bCondensed = true ) const; + +protected: + + bool ToStruct( const UStruct* StructType, void* StructPtr ) const; + +public: + + // Convert this object to structure memory. + TSharedPtr<FStructOnScope> ToStruct( const UStruct* StructType ) const; + + // Convert this object to a structure. + template<typename StructType> + StructType ToStruct() const + { + StructType Struct; + if ( ToStruct( StructType::StaticStruct(), &Struct ) ) + return Struct; + + return StructType(); + } + + // Convert this object to a linear color. + FLinearColor ToLinearColor() const; + + // Convert this object to a rotator. + FRotator ToRotator() const; + // Convert this object to a transform. + FTransform ToTransform() const; + // Convert this object to a vector. + FVector ToVector() const; + + // Copy a JSON object to a map of JSON values. + TMap<FString, FJsonLibraryValue> ToMap() const; + + // Copy a JSON object to a map of booleans. + TMap<FString, bool> ToBooleanMap() const; + // Copy a JSON object to a map of floats. + TMap<FString, float> ToFloatMap() const; + // Copy a JSON object to a map of integers. + TMap<FString, int32> ToIntegerMap() const; + // Copy a JSON object to a map of numbers. + TMap<FString, double> ToNumberMap() const; + // Copy a JSON object to a map of strings. + TMap<FString, FString> ToStringMap() const; + + // Copy a JSON object to a map of date/times. + TMap<FString, FDateTime> ToDateTimeMap() const; + // Copy a JSON object to a map of GUIDs. + TMap<FString, FGuid> ToGuidMap() const; + + // Copy a JSON object to a map of colors. + TMap<FString, FColor> ToColorMap() const; + // Copy a JSON object to a map of linear colors. + TMap<FString, FLinearColor> ToLinearColorMap() const; + + // Copy a JSON object to a map of rotators. + TMap<FString, FRotator> ToRotatorMap() const; + // Copy a JSON object to a map of transforms. + TMap<FString, FTransform> ToTransformMap() const; + // Copy a JSON object to a map of vectors. + TMap<FString, FVector> ToVectorMap() const; + + bool operator==( const FJsonLibraryObject& Object ) const; + bool operator!=( const FJsonLibraryObject& Object ) const; + + bool operator==( const FJsonLibraryValue& Value ) const; + bool operator!=( const FJsonLibraryValue& Value ) const; +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryValue.h b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryValue.h new file mode 100644 index 0000000000000000000000000000000000000000..b76ef00c1051ce54784972912be855666f1b2c67 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibrary/Public/JsonLibraryValue.h @@ -0,0 +1,165 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "Dom/JsonValue.h" +#include "Dom/JsonObject.h" +#include "Serialization/JsonReader.h" +#include "Serialization/JsonSerializer.h" +#include "Serialization/JsonTypes.h" +#include "JsonLibraryEnums.h" +#include "JsonLibraryValue.generated.h" + +typedef struct FJsonLibraryObject FJsonLibraryObject; +typedef struct FJsonLibraryList FJsonLibraryList; + +USTRUCT(BlueprintType, meta = (DisplayName = "JSON Value")) +struct JSONLIBRARY_API FJsonLibraryValue +{ + friend struct FJsonLibraryList; + friend struct FJsonLibraryObject; + + GENERATED_USTRUCT_BODY() + +protected: + + FJsonLibraryValue( const TSharedPtr<FJsonValue>& Value ); + +public: + + FJsonLibraryValue(); + + FJsonLibraryValue( bool Value ); + FJsonLibraryValue( float Value ); + FJsonLibraryValue( double Value ); + FJsonLibraryValue( int8 Value ); + FJsonLibraryValue( uint8 Value ); + FJsonLibraryValue( int16 Value ); + FJsonLibraryValue( uint16 Value ); + FJsonLibraryValue( int32 Value ); + FJsonLibraryValue( uint32 Value ); + FJsonLibraryValue( int64 Value ); + FJsonLibraryValue( uint64 Value ); + FJsonLibraryValue( const FString& Value ); + + FJsonLibraryValue( const FDateTime& Value ); + FJsonLibraryValue( const FGuid& Value ); + + FJsonLibraryValue( const FColor& Value ); + FJsonLibraryValue( const FLinearColor& Value ); + + FJsonLibraryValue( const FRotator& Value ); + FJsonLibraryValue( const FTransform& Value ); + FJsonLibraryValue( const FVector& Value ); + + FJsonLibraryValue( const FJsonLibraryObject& Value ); + FJsonLibraryValue( const FJsonLibraryList& Value ); + + FJsonLibraryValue( const TArray<FJsonLibraryValue>& Value ); + FJsonLibraryValue( const TMap<FString, FJsonLibraryValue>& Value ); + + // Get the JSON type of this value. + EJsonLibraryType GetType() const; + + // Check if this value equals another JSON value. + bool Equals( const FJsonLibraryValue& Value, bool bStrict = false ) const; + + // Convert this value to a boolean. + bool GetBoolean() const; + // Convert this value to a float. + float GetFloat() const; + // Convert this value to an integer. + int32 GetInteger() const; + // Convert this value to a number. + double GetNumber() const; + // Convert this value to a string. + FString GetString() const; + + // Convert this value to a date/time. + FDateTime GetDateTime() const; + // Convert this value to a GUID. + FGuid GetGuid() const; + + // Convert this value to a color. + FColor GetColor() const; + // Convert this value to a linear color. + FLinearColor GetLinearColor() const; + + // Convert this value to a rotator. + FRotator GetRotator() const; + // Convert this value to a transform. + FTransform GetTransform() const; + // Convert this value to a vector. + FVector GetVector() const; + + // Convert this value to a JSON object. + FJsonLibraryObject GetObject() const; + // Convert this value to a JSON array. + FJsonLibraryList GetList() const; + + // Convert this value to a 8-bit signed integer. + int8 GetInt8() const; + // Convert this value to a 16-bit signed integer. + int16 GetInt16() const; + // Convert this value to a 32-bit signed integer. + int32 GetInt32() const; + // Convert this value to a 64-bit signed integer. + int64 GetInt64() const; + // Convert this value to a 8-bit unsigned integer. + uint8 GetUInt8() const; + // Convert this value to a 16-bit unsigned integer. + uint16 GetUInt16() const; + // Convert this value to a 32-bit unsigned integer. + uint32 GetUInt32() const; + // Convert this value to a 64-bit unsigned integer. + uint64 GetUInt64() const; + +protected: + + TSharedPtr<FJsonValue> JsonValue; + + bool TryParse( const FString& Text, bool bStripComments = false, bool bStripTrailingCommas = false ); + bool TryStringify( FString& Text, bool bCondensed = true ) const; + +public: + + // Check if this value is valid. + bool IsValid() const; + + // Check if this value is a date/time. + bool IsDateTime() const; + // Check if this value is a GUID. + bool IsGuid() const; + + // Check if this value is a color. + bool IsColor() const; + // Check if this value is a linear color. + bool IsLinearColor() const; + + // Check if this value is a rotator. + bool IsRotator() const; + // Check if this value is a transform. + bool IsTransform() const; + // Check if this value is a vector. + bool IsVector() const; + + // Parse a JSON string. + static FJsonLibraryValue Parse( const FString& Text ); + // Parse a relaxed JSON string. + static FJsonLibraryValue ParseRelaxed( const FString& Text, bool bStripComments = true, bool bStripTrailingCommas = true ); + + // Stringify this value as a JSON string. + FString Stringify( bool bCondensed = true ) const; + + // Copy this value to an array of JSON values. + TArray<FJsonLibraryValue> ToArray() const; + // Copy this value to a map of JSON values. + TMap<FString, FJsonLibraryValue> ToMap() const; + + bool operator==( const FJsonLibraryValue& Value ) const; + bool operator!=( const FJsonLibraryValue& Value ) const; + + bool operator==( const FJsonLibraryObject& Object ) const; + bool operator!=( const FJsonLibraryObject& Object ) const; + + bool operator==( const FJsonLibraryList& List ) const; + bool operator!=( const FJsonLibraryList& List ) const; +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/JsonLibraryBlueprintSupport.Build.cs b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/JsonLibraryBlueprintSupport.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..e59629f73f5b453ba3aec5c7b9fbac1f3346dd26 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/JsonLibraryBlueprintSupport.Build.cs @@ -0,0 +1,25 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +namespace UnrealBuildTool.Rules +{ + public class JsonLibraryBlueprintSupport : ModuleRules + { + public JsonLibraryBlueprintSupport(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "UnrealEd", + "Slate", + "SlateCore", + "KismetWidgets", + "KismetCompiler", + "BlueprintGraph", + "JsonLibrary" + } + ); + } + } +} diff --git a/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/JsonLibraryBlueprintSupportModule.cpp b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/JsonLibraryBlueprintSupportModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..eb68c28f50e1db6af6e6b33bb726f54396a7730a --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/JsonLibraryBlueprintSupportModule.cpp @@ -0,0 +1,4 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "Modules/ModuleManager.h" + +IMPLEMENT_MODULE( FDefaultModuleImpl, JsonLibraryBlueprintSupport ); diff --git a/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/K2Node_JsonLibraryFromStruct.cpp b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/K2Node_JsonLibraryFromStruct.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5e5707229d6425923add11597aea452323db5b3a --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/K2Node_JsonLibraryFromStruct.cpp @@ -0,0 +1,305 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "K2Node_JsonLibraryFromStruct.h" +#include "Engine/UserDefinedStruct.h" +#include "EdGraph/EdGraphPin.h" +#include "EdGraphSchema_K2.h" +#include "K2Node_CallFunction.h" +#include "K2Node_IfThenElse.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "KismetCompiler.h" +#include "BlueprintActionDatabaseRegistrar.h" +#include "BlueprintNodeSpawner.h" +#include "EditorCategoryUtils.h" +#include "JsonLibraryObject.h" +#include "JsonLibraryBlueprintHelpers.h" + +#define LOCTEXT_NAMESPACE "K2Node_JsonLibraryFromStruct" + +struct UK2Node_JsonLibraryFromStructHelper +{ + static FName DataPinName; + static FName FailedPinName; +}; + +FName UK2Node_JsonLibraryFromStructHelper::FailedPinName( *LOCTEXT( "FailedPinName", "Failed" ).ToString() ); +FName UK2Node_JsonLibraryFromStructHelper::DataPinName( *LOCTEXT( "DataPinName", "Structure" ).ToString() ); + +UK2Node_JsonLibraryFromStruct::UK2Node_JsonLibraryFromStruct( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + NodeTooltip = LOCTEXT( "NodeTooltip", "Attempts to parse a JSON object into a structure." ); +} + +void UK2Node_JsonLibraryFromStruct::AllocateDefaultPins() +{ + const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); + CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute ); + + UEdGraphPin* SuccessPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then ); + SuccessPin->PinFriendlyName = LOCTEXT( "JsonLibraryFromStruct Success Exec pin", "Success" ); + + UEdGraphPin* FailedPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Exec, UK2Node_JsonLibraryFromStructHelper::FailedPinName ); + FailedPin->PinFriendlyName = LOCTEXT( "JsonLibraryFromStruct Failed Exec pin", "Failure" ); + + UEdGraphPin* DataPin = CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_Wildcard, UK2Node_JsonLibraryFromStructHelper::DataPinName ); + DataPin->bDisplayAsMutableRef = true; + SetPinToolTip( *DataPin, LOCTEXT( "DataPinDescription", "The structure to convert." ) ); + + UScriptStruct* JsonObjectStruct = TBaseStructure<FJsonLibraryObject>::Get(); + UEdGraphPin* ResultPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Struct, JsonObjectStruct, UEdGraphSchema_K2::PN_ReturnValue ); + ResultPin->PinFriendlyName = LOCTEXT( "JsonLibraryFromStruct Out Json", "Object" ); + SetPinToolTip( *ResultPin, LOCTEXT( "ResultPinDescription", "The returned JSON object, if converted." ) ); + + Super::AllocateDefaultPins(); +} + +void UK2Node_JsonLibraryFromStruct::SetPinToolTip( UEdGraphPin& MutatablePin, const FText& PinDescription ) const +{ + MutatablePin.PinToolTip = UEdGraphSchema_K2::TypeToText( MutatablePin.PinType ).ToString(); + + UEdGraphSchema_K2 const* const K2Schema = Cast<const UEdGraphSchema_K2>( GetSchema() ); + if ( K2Schema ) + { + MutatablePin.PinToolTip += TEXT( " " ); + MutatablePin.PinToolTip += K2Schema->GetPinDisplayName( &MutatablePin ).ToString(); + } + + MutatablePin.PinToolTip += FString( TEXT( "\n" ) ) + PinDescription.ToString(); +} + +void UK2Node_JsonLibraryFromStruct::RefreshInputPinType() +{ + UEdGraphPin* DataPin = GetDataPin(); + const bool bFillTypeFromConnected = DataPin && ( DataPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard ); + + UScriptStruct* InputType = nullptr; + if ( bFillTypeFromConnected ) + { + FEdGraphPinType PinType = DataPin->PinType; + if ( DataPin->LinkedTo.Num() > 0 ) + PinType = DataPin->LinkedTo[ 0 ]->PinType; + + if ( PinType.PinCategory == UEdGraphSchema_K2::PC_Struct ) + InputType = Cast<UScriptStruct>( PinType.PinSubCategoryObject.Get() ); + } + + SetPropertyTypeForStruct( InputType ); +} + +void UK2Node_JsonLibraryFromStruct::SetPropertyTypeForStruct( UScriptStruct* StructType ) +{ + if ( StructType == GetPropertyTypeForStruct() ) + return; + + UEdGraphPin* DataPin = GetDataPin(); + if ( DataPin->SubPins.Num() > 0 ) + GetSchema()->RecombinePin( DataPin ); + + DataPin->PinType.PinSubCategoryObject = StructType; + DataPin->PinType.PinCategory = StructType ? + UEdGraphSchema_K2::PC_Struct : + UEdGraphSchema_K2::PC_Wildcard; + + CachedNodeTitle.Clear(); +} + +UScriptStruct* UK2Node_JsonLibraryFromStruct::GetPropertyTypeForStruct() const +{ + UScriptStruct* DataStructType = (UScriptStruct*)( GetDataPin()->PinType.PinSubCategoryObject.Get() ); + return DataStructType; +} + +void UK2Node_JsonLibraryFromStruct::GetMenuActions( FBlueprintActionDatabaseRegistrar& ActionRegistrar ) const +{ + UClass* ActionKey = GetClass(); + if ( ActionRegistrar.IsOpenForRegistration( ActionKey ) ) + { + UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create( GetClass() ); + check( NodeSpawner != nullptr ); + + ActionRegistrar.AddBlueprintAction( ActionKey, NodeSpawner ); + } +} + +FText UK2Node_JsonLibraryFromStruct::GetMenuCategory() const +{ + return FText::FromString( TEXT( "JSON Library|Structure" ) ); +} + +bool UK2Node_JsonLibraryFromStruct::IsConnectionDisallowed( const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason ) const +{ + if ( MyPin == GetDataPin() && MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard ) + { + bool bDisallowed = true; + if ( OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct ) + { + if ( UScriptStruct* ConnectionType = Cast<UScriptStruct>( OtherPin->PinType.PinSubCategoryObject.Get() ) ) + bDisallowed = false; + } + else if ( OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard ) + bDisallowed = false; + + if ( bDisallowed ) + OutReason = TEXT( "Must be a structure." ); + + return bDisallowed; + } + + return false; +} + +FText UK2Node_JsonLibraryFromStruct::GetTooltipText() const +{ + return NodeTooltip; +} + +UEdGraphPin* UK2Node_JsonLibraryFromStruct::GetThenPin()const +{ + const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); + + UEdGraphPin* Pin = FindPinChecked( UEdGraphSchema_K2::PN_Then ); + check( Pin->Direction == EGPD_Output ); + return Pin; +} + +UEdGraphPin* UK2Node_JsonLibraryFromStruct::GetDataPin() const +{ + UEdGraphPin* Pin = FindPinChecked( UK2Node_JsonLibraryFromStructHelper::DataPinName ); + check( Pin->Direction == EGPD_Input ); + return Pin; +} + +UEdGraphPin* UK2Node_JsonLibraryFromStruct::GetFailedPin() const +{ + UEdGraphPin* Pin = FindPinChecked( UK2Node_JsonLibraryFromStructHelper::FailedPinName ); + check( Pin->Direction == EGPD_Output ); + return Pin; +} + +UEdGraphPin* UK2Node_JsonLibraryFromStruct::GetResultPin() const +{ + const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); + + UEdGraphPin* Pin = FindPinChecked( UEdGraphSchema_K2::PN_ReturnValue ); + check( Pin->Direction == EGPD_Output ); + return Pin; +} + +FText UK2Node_JsonLibraryFromStruct::GetNodeTitle( ENodeTitleType::Type TitleType ) const +{ + if ( TitleType == ENodeTitleType::MenuTitle ) + return LOCTEXT( "ListViewTitle", "Structure to JSON" ); + + if ( UEdGraphPin* DataPin = GetDataPin() ) + { + UScriptStruct* StructType = GetPropertyTypeForStruct(); + if ( !StructType || DataPin->LinkedTo.Num() == 0 ) + return NSLOCTEXT( "K2Node", "JsonFromStruct_Title_None", "Structure to JSON" ); + + if ( CachedNodeTitle.IsOutOfDate( this ) ) + { + FFormatNamedArguments Args; + Args.Add( TEXT( "StructName" ), FText::FromName( StructType->GetFName() ) ); + + FText LocFormat = NSLOCTEXT( "K2Node", "JsonFromStruct", "{StructName} to JSON" ); + CachedNodeTitle.SetCachedText( FText::Format( LocFormat, Args ), this ); + } + } + else + return NSLOCTEXT( "K2Node", "JsonFromStruct_Title_None", "Structure to JSON" ); + + return CachedNodeTitle; +} + +void UK2Node_JsonLibraryFromStruct::ExpandNode( FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph ) +{ + Super::ExpandNode( CompilerContext, SourceGraph ); + + const FName StructToJsonFunctionName = GET_FUNCTION_NAME_CHECKED( UJsonLibraryBlueprintHelpers, StructToJson ); + UK2Node_CallFunction* CallStructToJsonFunction = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>( this, SourceGraph ); + + CallStructToJsonFunction->FunctionReference.SetExternalMember( StructToJsonFunctionName, UJsonLibraryBlueprintHelpers::StaticClass() ); + CallStructToJsonFunction->AllocateDefaultPins(); + + CompilerContext.MovePinLinksToIntermediate( *GetExecPin(), *( CallStructToJsonFunction->GetExecPin() ) ); + + UScriptStruct* StructType = GetPropertyTypeForStruct(); + UUserDefinedStruct* UserStructType = Cast<UUserDefinedStruct>( StructType ); + + UEdGraphPin* StructTypePin = CallStructToJsonFunction->FindPinChecked( TEXT( "StructType" ) ); + if ( UserStructType && UserStructType->PrimaryStruct.IsValid() ) + StructTypePin->DefaultObject = UserStructType->PrimaryStruct.Get(); + else + StructTypePin->DefaultObject = StructType; + + UEdGraphPin* OriginalDataPin = GetDataPin(); + UEdGraphPin* StructInPin = CallStructToJsonFunction->FindPinChecked( TEXT( "Struct" ) ); + + StructInPin->PinType = OriginalDataPin->PinType; + StructInPin->PinType.PinSubCategoryObject = OriginalDataPin->PinType.PinSubCategoryObject; + + CompilerContext.MovePinLinksToIntermediate( *OriginalDataPin, *StructInPin ); + + UEdGraphPin* OriginalReturnPin = FindPinChecked( UEdGraphSchema_K2::PN_ReturnValue ); + UEdGraphPin* FunctionReturnPin = CallStructToJsonFunction->FindPinChecked( UEdGraphSchema_K2::PN_ReturnValue ); + UEdGraphPin* FunctionThenPin = CallStructToJsonFunction->GetThenPin(); + + const FName IsValidObjectFunctionName = GET_FUNCTION_NAME_CHECKED( UJsonLibraryBlueprintHelpers, IsValidObject ); + UK2Node_CallFunction* CallIsValidObjectFunction = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>( this, SourceGraph ); + + CallIsValidObjectFunction->FunctionReference.SetExternalMember( IsValidObjectFunctionName, UJsonLibraryBlueprintHelpers::StaticClass() ); + CallIsValidObjectFunction->bIsPureFunc = true; + CallIsValidObjectFunction->AllocateDefaultPins(); + + UEdGraphPin* ObjectInPin = CallIsValidObjectFunction->FindPinChecked( TEXT( "Object" ) ); + UEdGraphPin* CallReturnPin = CallIsValidObjectFunction->FindPinChecked( UEdGraphSchema_K2::PN_ReturnValue ); + + FunctionReturnPin->MakeLinkTo( ObjectInPin ); + + UK2Node_IfThenElse* BranchNode = CompilerContext.SpawnIntermediateNode<UK2Node_IfThenElse>( this, SourceGraph ); + BranchNode->AllocateDefaultPins(); + + FunctionThenPin->MakeLinkTo( BranchNode->GetExecPin() ); + CallReturnPin->MakeLinkTo( BranchNode->GetConditionPin() ); + + CompilerContext.MovePinLinksToIntermediate( *GetThenPin(), *( BranchNode->GetThenPin() ) ); + CompilerContext.MovePinLinksToIntermediate( *GetFailedPin(), *( BranchNode->GetElsePin() ) ); + CompilerContext.MovePinLinksToIntermediate( *OriginalReturnPin, *FunctionReturnPin ); + + BreakAllNodeLinks(); +} + +FSlateIcon UK2Node_JsonLibraryFromStruct::GetIconAndTint( FLinearColor& OutColor ) const +{ + OutColor = GetNodeTitleColor(); + static FSlateIcon Icon( "EditorStyle", "Kismet.AllClasses.FunctionIcon" ); + return Icon; +} + +void UK2Node_JsonLibraryFromStruct::PostReconstructNode() +{ + Super::PostReconstructNode(); + RefreshInputPinType(); +} + +void UK2Node_JsonLibraryFromStruct::EarlyValidation( FCompilerResultsLog& MessageLog ) const +{ + Super::EarlyValidation( MessageLog ); + if ( UEdGraphPin* DataPin = GetDataPin() ) + { + if ( DataPin->LinkedTo.Num() == 0 ) + { + MessageLog.Error( *LOCTEXT( "MissingPins", "Missing pins in @@" ).ToString(), this ); + return; + } + } +} + +void UK2Node_JsonLibraryFromStruct::NotifyPinConnectionListChanged( UEdGraphPin* Pin ) +{ + Super::NotifyPinConnectionListChanged( Pin ); + if ( Pin == GetDataPin() ) + RefreshInputPinType(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/K2Node_JsonLibraryToStruct.cpp b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/K2Node_JsonLibraryToStruct.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b9b384fe280b1480895e4fb0b948627412467219 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Private/K2Node_JsonLibraryToStruct.cpp @@ -0,0 +1,292 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "K2Node_JsonLibraryToStruct.h" +#include "Engine/UserDefinedStruct.h" +#include "EdGraph/EdGraphPin.h" +#include "EdGraphSchema_K2.h" +#include "K2Node_CallFunction.h" +#include "K2Node_IfThenElse.h" +#include "Kismet2/BlueprintEditorUtils.h" +#include "KismetCompiler.h" +#include "BlueprintActionDatabaseRegistrar.h" +#include "BlueprintNodeSpawner.h" +#include "EditorCategoryUtils.h" +#include "JsonLibraryObject.h" +#include "JsonLibraryBlueprintHelpers.h" + +#define LOCTEXT_NAMESPACE "K2Node_JsonLibraryToStruct" + +struct UK2Node_JsonLibraryToStructHelper +{ + static FName DataPinName; + static FName FailedPinName; +}; + +FName UK2Node_JsonLibraryToStructHelper::FailedPinName( *LOCTEXT( "FailedPinName", "Failed" ).ToString() ); +FName UK2Node_JsonLibraryToStructHelper::DataPinName( *LOCTEXT( "DataPinName", "Object" ).ToString() ); + +UK2Node_JsonLibraryToStruct::UK2Node_JsonLibraryToStruct( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + NodeTooltip = LOCTEXT( "NodeTooltip", "Attempts to parse a JSON object into a structure." ); +} + +void UK2Node_JsonLibraryToStruct::AllocateDefaultPins() +{ + const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); + CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Execute ); + + UEdGraphPin* SuccessPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Exec, UEdGraphSchema_K2::PN_Then ); + SuccessPin->PinFriendlyName = LOCTEXT( "JsonLibraryToStruct Success Exec pin", "Success" ); + + UEdGraphPin* FailedPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Exec, UK2Node_JsonLibraryToStructHelper::FailedPinName ); + FailedPin->PinFriendlyName = LOCTEXT( "JsonLibraryToStruct Failed Exec pin", "Failure" ); + + UScriptStruct* JsonObjectStruct = TBaseStructure<FJsonLibraryObject>::Get(); + UEdGraphPin* DataPin = CreatePin( EGPD_Input, UEdGraphSchema_K2::PC_Struct, JsonObjectStruct, UK2Node_JsonLibraryToStructHelper::DataPinName ); + SetPinToolTip( *DataPin, LOCTEXT( "DataPinDescription", "The JSON object to convert." ) ); + + UEdGraphPin* ResultPin = CreatePin( EGPD_Output, UEdGraphSchema_K2::PC_Wildcard, UEdGraphSchema_K2::PN_ReturnValue ); + //ResultPin->bDisplayAsMutableRef = true; + ResultPin->PinFriendlyName = LOCTEXT( "JsonLibraryToStruct Out Struct", "Structure" ); + SetPinToolTip( *ResultPin, LOCTEXT( "ResultPinDescription", "The returned structure, if converted." ) ); + + Super::AllocateDefaultPins(); +} + +void UK2Node_JsonLibraryToStruct::SetPinToolTip( UEdGraphPin& MutatablePin, const FText& PinDescription ) const +{ + MutatablePin.PinToolTip = UEdGraphSchema_K2::TypeToText( MutatablePin.PinType ).ToString(); + + UEdGraphSchema_K2 const* const K2Schema = Cast<const UEdGraphSchema_K2>( GetSchema() ); + if ( K2Schema ) + { + MutatablePin.PinToolTip += TEXT( " " ); + MutatablePin.PinToolTip += K2Schema->GetPinDisplayName( &MutatablePin ).ToString(); + } + + MutatablePin.PinToolTip += FString( TEXT( "\n" ) ) + PinDescription.ToString(); +} + +void UK2Node_JsonLibraryToStruct::RefreshOutputPinType() +{ + UEdGraphPin* ResultPin = GetResultPin(); + const bool bFillTypeFromConnected = ResultPin && ( ResultPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard ); + + UScriptStruct* OutputType = nullptr; + if ( bFillTypeFromConnected ) + { + FEdGraphPinType PinType = ResultPin->PinType; + if ( ResultPin->LinkedTo.Num() > 0 ) + PinType = ResultPin->LinkedTo[ 0 ]->PinType; + + if ( PinType.PinCategory == UEdGraphSchema_K2::PC_Struct ) + OutputType = Cast<UScriptStruct>( PinType.PinSubCategoryObject.Get() ); + } + + SetReturnTypeForStruct( OutputType ); +} + +void UK2Node_JsonLibraryToStruct::SetReturnTypeForStruct( UScriptStruct* StructType ) +{ + if ( StructType == GetReturnTypeForStruct() ) + return; + + UEdGraphPin* ResultPin = GetResultPin(); + if ( ResultPin->SubPins.Num() > 0 ) + GetSchema()->RecombinePin( ResultPin ); + + ResultPin->PinType.PinSubCategoryObject = StructType; + ResultPin->PinType.PinCategory = StructType ? + UEdGraphSchema_K2::PC_Struct : + UEdGraphSchema_K2::PC_Wildcard; + + CachedNodeTitle.Clear(); +} + +UScriptStruct* UK2Node_JsonLibraryToStruct::GetReturnTypeForStruct() const +{ + UScriptStruct* ReturnStructType = (UScriptStruct*)( GetResultPin()->PinType.PinSubCategoryObject.Get() ); + return ReturnStructType; +} + +void UK2Node_JsonLibraryToStruct::GetMenuActions( FBlueprintActionDatabaseRegistrar& ActionRegistrar ) const +{ + UClass* ActionKey = GetClass(); + if ( ActionRegistrar.IsOpenForRegistration( ActionKey ) ) + { + UBlueprintNodeSpawner* NodeSpawner = UBlueprintNodeSpawner::Create( GetClass() ); + check( NodeSpawner != nullptr ); + + ActionRegistrar.AddBlueprintAction( ActionKey, NodeSpawner ); + } +} + +FText UK2Node_JsonLibraryToStruct::GetMenuCategory() const +{ + return FText::FromString( TEXT( "JSON Library|Structure" ) ); +} + +bool UK2Node_JsonLibraryToStruct::IsConnectionDisallowed( const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason ) const +{ + if ( MyPin == GetResultPin() && MyPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard ) + { + bool bDisallowed = true; + if ( OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Struct ) + { + if ( UScriptStruct* ConnectionType = Cast<UScriptStruct>( OtherPin->PinType.PinSubCategoryObject.Get() ) ) + bDisallowed = false; + } + else if ( OtherPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Wildcard ) + bDisallowed = false; + + if ( bDisallowed ) + OutReason = TEXT( "Must be a structure." ); + + return bDisallowed; + } + + return false; +} + +FText UK2Node_JsonLibraryToStruct::GetTooltipText() const +{ + return NodeTooltip; +} + +UEdGraphPin* UK2Node_JsonLibraryToStruct::GetThenPin()const +{ + const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); + + UEdGraphPin* Pin = FindPinChecked( UEdGraphSchema_K2::PN_Then ); + check( Pin->Direction == EGPD_Output ); + return Pin; +} + +UEdGraphPin* UK2Node_JsonLibraryToStruct::GetDataPin() const +{ + UEdGraphPin* Pin = FindPinChecked( UK2Node_JsonLibraryToStructHelper::DataPinName ); + check( Pin->Direction == EGPD_Input ); + return Pin; +} + +UEdGraphPin* UK2Node_JsonLibraryToStruct::GetFailedPin() const +{ + UEdGraphPin* Pin = FindPinChecked( UK2Node_JsonLibraryToStructHelper::FailedPinName ); + check( Pin->Direction == EGPD_Output ); + return Pin; +} + +UEdGraphPin* UK2Node_JsonLibraryToStruct::GetResultPin() const +{ + const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>(); + + UEdGraphPin* Pin = FindPinChecked( UEdGraphSchema_K2::PN_ReturnValue ); + check( Pin->Direction == EGPD_Output ); + return Pin; +} + +FText UK2Node_JsonLibraryToStruct::GetNodeTitle( ENodeTitleType::Type TitleType ) const +{ + if ( TitleType == ENodeTitleType::MenuTitle ) + return LOCTEXT( "ListViewTitle", "JSON to Structure" ); + + if ( UEdGraphPin* ResultPin = GetResultPin() ) + { + UScriptStruct* StructType = GetReturnTypeForStruct(); + if ( !StructType || ResultPin->LinkedTo.Num() == 0 ) + return NSLOCTEXT( "K2Node", "JsonToStruct_Title_None", "JSON to Structure" ); + + if ( CachedNodeTitle.IsOutOfDate( this ) ) + { + FFormatNamedArguments Args; + Args.Add( TEXT( "StructName" ), FText::FromName( StructType->GetFName() ) ); + + FText LocFormat = NSLOCTEXT( "K2Node", "JsonToStruct", "JSON to {StructName}" ); + CachedNodeTitle.SetCachedText( FText::Format( LocFormat, Args ), this ); + } + } + else + return NSLOCTEXT( "K2Node", "JsonToStruct_Title_None", "JSON to Structure" ); + + return CachedNodeTitle; +} + +void UK2Node_JsonLibraryToStruct::ExpandNode( FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph ) +{ + Super::ExpandNode( CompilerContext, SourceGraph ); + + const FName StructFromJsonFunctionName = GET_FUNCTION_NAME_CHECKED( UJsonLibraryBlueprintHelpers, StructFromJson ); + UK2Node_CallFunction* CallStructFromJsonFunction = CompilerContext.SpawnIntermediateNode<UK2Node_CallFunction>( this, SourceGraph ); + + CallStructFromJsonFunction->FunctionReference.SetExternalMember( StructFromJsonFunctionName, UJsonLibraryBlueprintHelpers::StaticClass() ); + CallStructFromJsonFunction->AllocateDefaultPins(); + + CompilerContext.MovePinLinksToIntermediate( *GetExecPin(), *( CallStructFromJsonFunction->GetExecPin() ) ); + + UScriptStruct* StructType = GetReturnTypeForStruct(); + UUserDefinedStruct* UserStructType = Cast<UUserDefinedStruct>( StructType ); + + UEdGraphPin* StructTypePin = CallStructFromJsonFunction->FindPinChecked( TEXT( "StructType" ) ); + if ( UserStructType && UserStructType->PrimaryStruct.IsValid() ) + StructTypePin->DefaultObject = UserStructType->PrimaryStruct.Get(); + else + StructTypePin->DefaultObject = StructType; + + UEdGraphPin* DataInPin = CallStructFromJsonFunction->FindPinChecked( TEXT( "Object" ) ); + CompilerContext.MovePinLinksToIntermediate( *GetDataPin(), *DataInPin ); + + UEdGraphPin* OriginalReturnPin = FindPinChecked( UEdGraphSchema_K2::PN_ReturnValue ); + UEdGraphPin* FunctionOutStructPin = CallStructFromJsonFunction->FindPinChecked( TEXT( "OutStruct" ) ); + UEdGraphPin* FunctionReturnPin = CallStructFromJsonFunction->FindPinChecked( UEdGraphSchema_K2::PN_ReturnValue ); + UEdGraphPin* FunctionThenPin = CallStructFromJsonFunction->GetThenPin(); + + FunctionOutStructPin->PinType = OriginalReturnPin->PinType; + FunctionOutStructPin->PinType.PinSubCategoryObject = OriginalReturnPin->PinType.PinSubCategoryObject; + + UK2Node_IfThenElse* BranchNode = CompilerContext.SpawnIntermediateNode<UK2Node_IfThenElse>( this, SourceGraph ); + BranchNode->AllocateDefaultPins(); + + FunctionThenPin->MakeLinkTo( BranchNode->GetExecPin() ); + FunctionReturnPin->MakeLinkTo( BranchNode->GetConditionPin() ); + + CompilerContext.MovePinLinksToIntermediate( *GetThenPin(), *( BranchNode->GetThenPin() ) ); + CompilerContext.MovePinLinksToIntermediate( *GetFailedPin(), *( BranchNode->GetElsePin() ) ); + CompilerContext.MovePinLinksToIntermediate( *OriginalReturnPin, *FunctionOutStructPin ); + + BreakAllNodeLinks(); +} + +FSlateIcon UK2Node_JsonLibraryToStruct::GetIconAndTint( FLinearColor& OutColor ) const +{ + OutColor = GetNodeTitleColor(); + static FSlateIcon Icon( "EditorStyle", "Kismet.AllClasses.FunctionIcon" ); + return Icon; +} + +void UK2Node_JsonLibraryToStruct::PostReconstructNode() +{ + Super::PostReconstructNode(); + RefreshOutputPinType(); +} + +void UK2Node_JsonLibraryToStruct::EarlyValidation( FCompilerResultsLog& MessageLog ) const +{ + Super::EarlyValidation( MessageLog ); + if ( UEdGraphPin* ResultPin = GetResultPin() ) + { + if ( ResultPin->LinkedTo.Num() == 0 ) + { + MessageLog.Error( *LOCTEXT( "MissingPins", "Missing pins in @@" ).ToString(), this ); + return; + } + } +} + +void UK2Node_JsonLibraryToStruct::NotifyPinConnectionListChanged( UEdGraphPin* Pin ) +{ + Super::NotifyPinConnectionListChanged( Pin ); + if ( Pin == GetResultPin() ) + RefreshOutputPinType(); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Public/K2Node_JsonLibraryFromStruct.h b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Public/K2Node_JsonLibraryFromStruct.h new file mode 100644 index 0000000000000000000000000000000000000000..6fed4ea0e5c0e59c4874f383123d47b02d92203b --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Public/K2Node_JsonLibraryFromStruct.h @@ -0,0 +1,48 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "EdGraph/EdGraphNodeUtils.h" +#include "UObject/ObjectMacros.h" +#include "Textures/SlateIcon.h" +#include "K2Node.h" +#include "K2Node_JsonLibraryFromStruct.generated.h" + +class FBlueprintActionDatabaseRegistrar; +class UEdGraph; + +UCLASS() +class JSONLIBRARYBLUEPRINTSUPPORT_API UK2Node_JsonLibraryFromStruct : public UK2Node +{ + GENERATED_UCLASS_BODY() + + virtual void AllocateDefaultPins() override; + virtual FText GetNodeTitle( ENodeTitleType::Type TitleType ) const override; + virtual FText GetTooltipText() const override; + virtual void ExpandNode( class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph ) override; + virtual FSlateIcon GetIconAndTint( FLinearColor& OutColor ) const override; + virtual void PostReconstructNode() override; + + virtual bool IsNodeSafeToIgnore() const override { return true; } + virtual void GetMenuActions( FBlueprintActionDatabaseRegistrar& ActionRegistrar ) const override; + virtual FText GetMenuCategory() const override; + virtual bool IsConnectionDisallowed( const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason ) const override; + virtual void EarlyValidation( class FCompilerResultsLog& MessageLog ) const override; + virtual void NotifyPinConnectionListChanged( UEdGraphPin* Pin ) override; + + UScriptStruct* GetPropertyTypeForStruct() const; + + UEdGraphPin* GetThenPin() const; + UEdGraphPin* GetDataPin() const; + UEdGraphPin* GetFailedPin() const; + UEdGraphPin* GetResultPin() const; + +private: + + void SetPinToolTip( UEdGraphPin& MutatablePin, const FText& PinDescription ) const; + + void SetPropertyTypeForStruct( UScriptStruct* InClass ); + void RefreshInputPinType(); + + FText NodeTooltip; + FNodeTextCache CachedNodeTitle; +}; diff --git a/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Public/K2Node_JsonLibraryToStruct.h b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Public/K2Node_JsonLibraryToStruct.h new file mode 100644 index 0000000000000000000000000000000000000000..3a6b9d1583401616ac7f377780bb15e1de172cb7 --- /dev/null +++ b/Plugins/JsonLibrary/Source/JsonLibraryBlueprintSupport/Public/K2Node_JsonLibraryToStruct.h @@ -0,0 +1,48 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "EdGraph/EdGraphNodeUtils.h" +#include "UObject/ObjectMacros.h" +#include "Textures/SlateIcon.h" +#include "K2Node.h" +#include "K2Node_JsonLibraryToStruct.generated.h" + +class FBlueprintActionDatabaseRegistrar; +class UEdGraph; + +UCLASS() +class JSONLIBRARYBLUEPRINTSUPPORT_API UK2Node_JsonLibraryToStruct : public UK2Node +{ + GENERATED_UCLASS_BODY() + + virtual void AllocateDefaultPins() override; + virtual FText GetNodeTitle( ENodeTitleType::Type TitleType ) const override; + virtual FText GetTooltipText() const override; + virtual void ExpandNode( class FKismetCompilerContext& CompilerContext, UEdGraph* SourceGraph ) override; + virtual FSlateIcon GetIconAndTint( FLinearColor& OutColor ) const override; + virtual void PostReconstructNode() override; + + virtual bool IsNodeSafeToIgnore() const override { return true; } + virtual void GetMenuActions( FBlueprintActionDatabaseRegistrar& ActionRegistrar ) const override; + virtual FText GetMenuCategory() const override; + virtual bool IsConnectionDisallowed( const UEdGraphPin* MyPin, const UEdGraphPin* OtherPin, FString& OutReason ) const override; + virtual void EarlyValidation( class FCompilerResultsLog& MessageLog ) const override; + virtual void NotifyPinConnectionListChanged( UEdGraphPin* Pin ) override; + + UScriptStruct* GetReturnTypeForStruct() const; + + UEdGraphPin* GetThenPin() const; + UEdGraphPin* GetDataPin() const; + UEdGraphPin* GetFailedPin() const; + UEdGraphPin* GetResultPin() const; + +private: + + void SetPinToolTip( UEdGraphPin& MutatablePin, const FText& PinDescription ) const; + + void SetReturnTypeForStruct( UScriptStruct* InClass ); + void RefreshOutputPinType(); + + FText NodeTooltip; + FNodeTextCache CachedNodeTitle; +}; diff --git a/Plugins/OculusXR/Config/BaseOculusXR.ini b/Plugins/OculusXR/Config/BaseOculusXR.ini new file mode 100644 index 0000000000000000000000000000000000000000..a64ac67220ed881f6e51256ddeee50bc14125df6 --- /dev/null +++ b/Plugins/OculusXR/Config/BaseOculusXR.ini @@ -0,0 +1,117 @@ +[CoreRedirects] +; ++PackageRedirects=(OldName="/OculusVR/",NewName="/OculusXR/",MatchSubstring=true) +; ++ClassRedirects=(OldName="/Script/OculusHMD.OculusResourceHolder", NewName="/Script/OculusXRHMD.OculusXRResourceHolder") ++ClassRedirects=(OldName="/Script/OculusHMD.OculusPassthroughLayerComponent", NewName="/Script/OculusXRHMD.OculusXRPassthroughLayerComponent") ++ClassRedirects=(OldName="/Script/OculusHMD.StereoLayerShapeUserDefined", NewName="/Script/OculusXRHMD.OculusXRStereoLayerShapeUserDefined") ++ClassRedirects=(OldName="/Script/OculusHMD.StereoLayerShapeReconstructed", NewName="/Script/OculusXRHMD.OculusXRStereoLayerShapeReconstructed") ++ClassRedirects=(OldName="/Script/OculusHMD.OculusHMDRuntimeSettings", NewName="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings") ++ClassRedirects=(OldName="/Script/OculusHMD.OculusEventComponent", NewName="/Script/OculusXRHMD.OculusXREventComponent") ++ClassRedirects=(OldName="/Script/OculusHMD.OculusSceneCaptureCubemap", NewName="/Script/OculusXRHMD.OculusXRSceneCaptureCubemap") ++ClassRedirects=(OldName="/Script/OculusHMD.PassthroughLayerBase", NewName="/Script/OculusXRHMD.OculusXRPassthroughLayerBase") ++ClassRedirects=(OldName="/Script/OculusHMD.OculusFunctionLibrary", NewName="/Script/OculusXRHMD.OculusXRFunctionLibrary") +; ++EnumRedirects=(OldName="EOculusXrApi", NewName="/Script/OculusXRHMD.EOculusXRXrApi") ++EnumRedirects=(OldName="EHandTrackingSupport", NewName="/Script/OculusXRHMD.EOculusXRHandTrackingSupport") ++EnumRedirects=(OldName="ETrackedDeviceType", NewName="/Script/OculusXRHMD.EOculusXRTrackedDeviceType") ++EnumRedirects=(OldName="EHandTrackingFrequency", NewName="/Script/OculusXRHMD.EOculusXRHandTrackingFrequency") ++EnumRedirects=(OldName="EColorMapType", NewName="/Script/OculusXRHMD.EOculusXRColorMapType") ++EnumRedirects=(OldName="EPassthroughLayerOrder", NewName="/Script/OculusXRHMD.EOculusXRPassthroughLayerOrder") ++EnumRedirects=(OldName="EOculusDeviceType", NewName="/Script/OculusXRHMD.EOculusXRDeviceType") ++EnumRedirects=(OldName="EColorSpace", NewName="/Script/OculusXRHMD.EOculusXRColorSpace") ++EnumRedirects=(OldName="EBoundaryType", NewName="/Script/OculusXRHMD.EOculusXRBoundaryType") ++EnumRedirects=(OldName="EProcessorPerformanceLevel", NewName="/Script/OculusXRHMD.EOculusXRProcessorPerformanceLevel") +; ++StructRedirects=(OldName="/Script/OculusHMD.GuardianTestResult", NewName="/Script/OculusXRHMD.OculusXRGuardianTestResult") ++StructRedirects=(OldName="/Script/OculusHMD.OculusSplashDesc", NewName="/Script/OculusXRHMD.OculusXRSplashDesc") ++StructRedirects=(OldName="/Script/OculusHMD.HmdUserProfile", NewName="/Script/OculusXRHMD.OculusXRHmdUserProfile") ++StructRedirects=(OldName="/Script/OculusHMD.HmdUserProfileField", NewName="/Script/OculusXRHMD.OculusXRHmdUserProfileField") +; ++ClassRedirects=(OldName="/Script/OculusInput.OculusHandComponent", NewName="/Script/OculusXRInput.OculusXRHandComponent") ++ClassRedirects=(OldName="/Script/OculusInput.OculusMRFunctionLibrary", NewName="/Script/OculusXRInput.OculusXRMRFunctionLibrary") ++ClassRedirects=(OldName="/Script/OculusInput.OculusInputFunctionLibrary", NewName="/Script/OculusXRInput.OculusXRInputFunctionLibrary") +; ++EnumRedirects=(OldName="ETrackingConfidence", NewName="/Script/OculusXRInput.EOculusXRTrackingConfidence") ++EnumRedirects=(OldName="EConfidenceBehavior", NewName="/Script/OculusXRInput.EOculusXRConfidenceBehavior") ++EnumRedirects=(OldName="EOculusHandType", NewName="/Script/OculusXRInput.EOculusXRHandType") ++EnumRedirects=(OldName="EOculusFinger", NewName="/Script/OculusXRInput.EOculusXRFinger") ++EnumRedirects=(OldName="ESystemGestureBehavior", NewName="/Script/OculusXRInput.EOculusXRSystemGestureBehavior") ++EnumRedirects=(OldName="EBone", NewName="/Script/OculusXRInput.EOculusXRBone") +; ++StructRedirects=(OldName="/Script/OculusInput.OculusCapsuleCollider", NewName="/Script/OculusXRInput.OculusXRCapsuleCollider") +; +; +; +; ++ClassRedirects=(OldName="/Script/OculusEditor.OculusHMDRuntimeSettings", NewName="/Script/OculusXREditor.OculusXRHMDRuntimeSettings") ++ClassRedirects=(OldName="/Script/OculusEditor.OculusEditorSettings", NewName="/Script/OculusXREditor.OculusXREditorSettings") ++ClassRedirects=(OldName="/Script/OculusEditor.OculusPlatformToolSettings", NewName="/Script/OculusXREditor.OculusXRPlatformToolSettings") +; ++EnumRedirects=(OldName="EOculusAssetType", NewName="/Script/OculusXREditor.EOculusXRAssetType") ++EnumRedirects=(OldName="EOculusPlatform", NewName="/Script/OculusXREditor.EOculusXRPlatform") ++EnumRedirects=(OldName="EOculusGamepadEmulation", NewName="/Script/OculusXREditor.EOculusXRGamepadEmulation") +; ++StructRedirects=(OldName="/Script/OculusEditor.RedistPackage", NewName="/Script/OculusXREditor.OculusXRRedistPackage") ++StructRedirects=(OldName="/Script/OculusEditor.AssetConfig", NewName="/Script/OculusXREditor.OculusXRAssetConfig") +; ++ClassRedirects=(OldName="/Script/OculusMR.OculusFunctionLibrary", NewName="/Script/OculusMR.OculusXRFunctionLibrary") ++ClassRedirects=(OldName="/Script/OculusMR.OculusMR_Settings", NewName="/Script/OculusMR.OculusXRMR_Settings") ++ClassRedirects=(OldName="/Script/OculusMR.OculusMRFunctionLibrary", NewName="/Script/OculusMR.OculusXRMRFunctionLibrary") +; ++EnumRedirects=(OldName="EOculusMR_CameraDeviceEnum", NewName="/Script/OculusMR.EOculusXRMR_CameraDeviceEnum") ++EnumRedirects=(OldName="EOculusMR_PostProcessEffects", NewName="/Script/OculusMR.EOculusXRMR_PostProcessEffects") ++EnumRedirects=(OldName="EOculusMR_CompositionMethod", NewName="/Script/OculusMR.EOculusXRMR_CompositionMethod") ++EnumRedirects=(OldName="EOculusMR_ClippingReference", NewName="/Script/OculusMR.EOculusXRMR_ClippingReference") +; ++StructRedirects=(OldName="/Script/OculusMR.OculusMR_PlaneMeshTriangle", NewName="/Script/OculusMR.OculusXRMR_PlaneMeshTriangle") ++StructRedirects=(OldName="/Script/OculusMR.TrackedCamera", NewName="/Script/OculusMR.OculusXRTrackedCamera") +; ++EnumRedirects=(OldName="EOculusXRXrApi",ValueChanges=(("LegacyOVRPlugin","OVRPluginOpenXR"))) +; +; Uncomment these lines for marketplace after Epic removes the ETiledMultiRes redirects from BaseEngine.ini, otherwise there will be conflicts +;+EnumRedirects=(OldName="ETiledMultiResLevel",NewName="EOculusXRFoveatedRenderingLevel",ValueChanges=(("ETiledMultiResLevel_Off","Off"),("ETiledMultiResLevel_LMSLow","Low"),("ETiledMultiResLevel_LMSMedium","Medium"),("ETiledMultiResLevel_LMSHigh","High"),("ETiledMultiResLevel_LMSHighTop","HighTop"))) +;+FunctionRedirects=(OldName="GetTiledMultiresLevel",NewName="GetFoveatedRenderingLevel") +;+FunctionRedirects=(OldName="SetTiledMultiresLevel",NewName="SetFoveatedRenderingLevel") +;+EnumRedirects=(OldName="EFixedFoveatedRenderingLevel",NewName="EOculusXRFoveatedRenderingLevel",ValueChanges=(("FFR_Off","Off"),("FFR_Low","Low"),("FFR_Medium","Medium"),("FFR_High","High"),("FFR_HighTop","HighTop"))) +;+FunctionRedirects=(OldName="GetFixedFoveatedRenderingLevel",NewName="GetFoveatedRenderingLevel") +;+FunctionRedirects=(OldName="SetFixedFoveatedRenderingLevel",NewName="SetFoveatedRenderingLevel") +; +; Anchors and Scene redirects ++StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryInfo", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryInfo") ++StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryResult", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryResult") ++StructRedirects=(OldName="/Script/OculusAnchors.OculusSpaceQueryFilterValues", NewName="/Script/OculusXRAnchors.OculusXRSpaceQueryFilterValues") ++StructRedirects=(OldName="/Script/OculusAnchors.OculusAnchorManager", NewName="/Script/OculusXRAnchors.OculusXRAnchorManager") ++StructRedirects=(OldName="/Script/OculusAnchors.OculusSpatialAnchorManager", NewName="/Script/OculusXRAnchors.OculusXRSpatialAnchorManager") ++StructRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayoutManager", NewName="/Script/OculusXRAnchors.OculusXRRoomLayoutManager") ++StructRedirects=(OldName="/Script/OculusAnchors.OculusAnchors", NewName="/Script/OculusXRAnchors.OculusXRAnchors") ++StructRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayout", NewName="/Script/OculusXRAnchors.OculusXRRoomLayout") ++StructRedirects=(OldName="/Script/OculusScene.OculusSpawnedSceneAnchorProperties", NewName="/Script/OculusXRScene.OculusXRSpawnedSceneAnchorProperties") ++StructRedirects=(OldName="/Script/OculusAnchors.UUID", NewName="/Script/OculusXRAnchors.OculusXRUUID") ++StructRedirects=(OldName="/Script/OculusAnchors.UInt64", NewName="/Script/OculusXRAnchors.OculusXRUInt64") +; ++EnumRedirects=(OldName="EOculusSpaceQueryFilterType", NewName="/Script/OculusXRAnchors.EOculusXRSpaceQueryFilterType") ++EnumRedirects=(OldName="EOculusSpaceStorageLocation", NewName="/Script/OculusXRAnchors.EOculusXRSpaceStorageLocation") ++EnumRedirects=(OldName="EOculusSpaceStoragePersistenceMode", NewName="/Script/OculusXRAnchors.EOculusXRSpaceStoragePersistenceMode") ++EnumRedirects=(OldName="EOculusSpaceComponentType", NewName="/Script/OculusXRAnchors.EOculusXRSpaceComponentType") ++EnumRedirects=(OldName="EOculusLaunchCaptureFlowWhenMissingScene", NewName="/Script/OculusXRScene.EOculusXRLaunchCaptureFlowWhenMissingScene") +; ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAnchorComponent", NewName="/Script/OculusXRAnchors.OculusXRAnchorComponent") ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAnchorBPFuctionLibrary", NewName="/Script/OculusXRAnchors.OculusXRAnchorBPFuctionLibrary") ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_CreateSpatialAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_CreateSpatialAnchor") ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_EraseAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_EraseAnchor") ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_SaveAnchor", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_SaveAnchor") ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_QueryAnchors", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_QueryAnchors") ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusAsyncAction_SetAnchorComponentStatus", NewName="/Script/OculusXRAnchors.OculusXRAsyncAction_SetAnchorComponentStatus") ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusRoomLayoutManagerComponent", NewName="/Script/OculusXRAnchors.OculusXRRoomLayoutManagerComponent") ++ClassRedirects=(OldName="/Script/OculusAnchors.OculusSpatialAnchorComponent", NewName="/Script/OculusXRAnchors.OculusXRSpatialAnchorComponent") ++ClassRedirects=(OldName="/Script/OculusScene.OculusSceneAnchorComponent", NewName="/Script/OculusXRScene.OculusXRSceneAnchorComponent") ++ClassRedirects=(OldName="/Script/OculusScene.OculusSceneActor", NewName="/Script/OculusXRScene.OculusXRSceneActor") +; ++FunctionRedirects=(OldName="OculusAsyncAction_CreateSpatialAnchor.OculusAsyncCreateSpatialAnchor",NewName="OculusXRAsyncAction_CreateSpatialAnchor.OculusXRAsyncCreateSpatialAnchor") ++FunctionRedirects=(OldName="OculusAsyncAction_EraseAnchor.OculusAsyncEraseAnchor",NewName="OculusXRAsyncAction_EraseAnchor.OculusXRAsyncEraseAnchor") ++FunctionRedirects=(OldName="OculusAsyncAction_SaveAnchor.OculusAsyncSaveAnchor",NewName="OculusXRAsyncAction_SaveAnchor.OculusXRAsyncSaveAnchor") ++FunctionRedirects=(OldName="OculusAsyncAction_QueryAnchors.OculusAsyncQueryAnchors",NewName="OculusXRAsyncAction_QueryAnchors.OculusXRAsyncQueryAnchors") ++FunctionRedirects=(OldName="OculusAsyncAction_QueryAnchors.OculusAsyncQueryAnchorsAdvanced",NewName="OculusXRAsyncAction_QueryAnchors.OculusXRAsyncQueryAnchorsAdvanced") ++FunctionRedirects=(OldName="OculusAsyncAction_SetAnchorComponentStatus.OculusAsyncSetAnchorComponentStatus",NewName="OculusXRAsyncAction_SetAnchorComponentStatus.OculusXRAsyncSetAnchorComponentStatus") +; diff --git a/Plugins/OculusXR/Config/FilterPlugin.ini b/Plugins/OculusXR/Config/FilterPlugin.ini new file mode 100644 index 0000000000000000000000000000000000000000..71f9fee678cf2d6d7323b4913e67e51f70d7422e --- /dev/null +++ b/Plugins/OculusXR/Config/FilterPlugin.ini @@ -0,0 +1,10 @@ +[FilterPlugin] +; This section lists additional files which will be packaged along with your plugin. Paths should be listed relative to the root plugin directory, and +; may include "...", "*", and "?" wildcards to match directories, files, and individual characters respectively. +; +; Examples: +; /README.txt +; /Extras/... +; /Binaries/ThirdParty/*.dll +/Config/BaseOculusXR.ini +/build.log diff --git a/Plugins/OculusXR/Content/Materials/LeftMetaQuestTouchProBatteryIndicatorMaterial.uasset b/Plugins/OculusXR/Content/Materials/LeftMetaQuestTouchProBatteryIndicatorMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..f407d6b115bb8e51c0c92aaf286dd2679b11df8b Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/LeftMetaQuestTouchProBatteryIndicatorMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/LeftMetaQuestTouchProMaterial.uasset b/Plugins/OculusXR/Content/Materials/LeftMetaQuestTouchProMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..613e6271aad818ef9a01d11b104b92dfd2d53469 Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/LeftMetaQuestTouchProMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/OculusMR_ChromaKey.uasset b/Plugins/OculusXR/Content/Materials/OculusMR_ChromaKey.uasset new file mode 100644 index 0000000000000000000000000000000000000000..9b628d3682e4bcd671d7f2d09313c0895a7597f6 Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/OculusMR_ChromaKey.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/OculusMR_OpaqueColoredMaterial.uasset b/Plugins/OculusXR/Content/Materials/OculusMR_OpaqueColoredMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..da45cb87244648ea1960a7646b2763ed720950c1 Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/OculusMR_OpaqueColoredMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/OculusMR_WhiteMaterial.uasset b/Plugins/OculusXR/Content/Materials/OculusMR_WhiteMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..a76dfa3f92ac1fdb7cdc24d4a2f45e933501a87c Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/OculusMR_WhiteMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/PokeAHoleInverseMaterial.uasset b/Plugins/OculusXR/Content/Materials/PokeAHoleInverseMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5d76114468adb892ff60448d68d6563324191b70 Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/PokeAHoleInverseMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/PokeAHoleMaterial.uasset b/Plugins/OculusXR/Content/Materials/PokeAHoleMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7b8a5178431504b1c3bc6a73568c0d9aeede37d4 Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/PokeAHoleMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/RightMetaQuestTouchProBatteryIndicatorMaterial.uasset b/Plugins/OculusXR/Content/Materials/RightMetaQuestTouchProBatteryIndicatorMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d85abc966b90a000a1cfb9e9a50680696523ae18 Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/RightMetaQuestTouchProBatteryIndicatorMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/RightMetaQuestTouchProMaterial.uasset b/Plugins/OculusXR/Content/Materials/RightMetaQuestTouchProMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..c51153fe019642f09532a7738614f745c87cdfb7 Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/RightMetaQuestTouchProMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/TouchForQuest2Material.uasset b/Plugins/OculusXR/Content/Materials/TouchForQuest2Material.uasset new file mode 100644 index 0000000000000000000000000000000000000000..32169ddaf7938ade692cc3c64eb16b12425683bf Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/TouchForQuest2Material.uasset differ diff --git a/Plugins/OculusXR/Content/Materials/TouchForQuestRiftSControllerMaterial.uasset b/Plugins/OculusXR/Content/Materials/TouchForQuestRiftSControllerMaterial.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1d0eb8c75d60165e53617384758a1968cd698460 Binary files /dev/null and b/Plugins/OculusXR/Content/Materials/TouchForQuestRiftSControllerMaterial.uasset differ diff --git a/Plugins/OculusXR/Content/Meshes/LeftMetaQuestTouchPro.uasset b/Plugins/OculusXR/Content/Meshes/LeftMetaQuestTouchPro.uasset new file mode 100644 index 0000000000000000000000000000000000000000..d4c47ac0fa03562a92f5057c257fff2c6244ff06 Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/LeftMetaQuestTouchPro.uasset differ diff --git a/Plugins/OculusXR/Content/Meshes/LeftMetaQuestTouchProBatteryIndicatorQuad.uasset b/Plugins/OculusXR/Content/Meshes/LeftMetaQuestTouchProBatteryIndicatorQuad.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ace2fa182b7aa260f38974052e9e8c640dbd9132 Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/LeftMetaQuestTouchProBatteryIndicatorQuad.uasset differ diff --git a/Plugins/OculusXR/Content/Meshes/LeftTouchForQuest2.uasset b/Plugins/OculusXR/Content/Meshes/LeftTouchForQuest2.uasset new file mode 100644 index 0000000000000000000000000000000000000000..b17f0a661c85b7296c952ea8aecdd20a962f35da Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/LeftTouchForQuest2.uasset differ diff --git a/Plugins/OculusXR/Content/Meshes/LeftTouchForQuestRiftSController.fbx b/Plugins/OculusXR/Content/Meshes/LeftTouchForQuestRiftSController.fbx new file mode 100644 index 0000000000000000000000000000000000000000..04a438eadacf00dd6cafcbdf49066d331fc093a9 Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/LeftTouchForQuestRiftSController.fbx differ diff --git a/Plugins/OculusXR/Content/Meshes/LeftTouchForQuestRiftSController.uasset b/Plugins/OculusXR/Content/Meshes/LeftTouchForQuestRiftSController.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1240ac74725e23ed8933c82a95c90843fcbda6da Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/LeftTouchForQuestRiftSController.uasset differ diff --git a/Plugins/OculusXR/Content/Meshes/RightMetaQuestTouchPro.uasset b/Plugins/OculusXR/Content/Meshes/RightMetaQuestTouchPro.uasset new file mode 100644 index 0000000000000000000000000000000000000000..cf4224bd75e9bd100896b816ef6b93f3836dfb78 Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/RightMetaQuestTouchPro.uasset differ diff --git a/Plugins/OculusXR/Content/Meshes/RightMetaQuestTouchProBatteryIndicatorQuad.uasset b/Plugins/OculusXR/Content/Meshes/RightMetaQuestTouchProBatteryIndicatorQuad.uasset new file mode 100644 index 0000000000000000000000000000000000000000..1b4938147fc6cae2b7de42a0773db3c8315fd370 Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/RightMetaQuestTouchProBatteryIndicatorQuad.uasset differ diff --git a/Plugins/OculusXR/Content/Meshes/RightTouchForQuest2.uasset b/Plugins/OculusXR/Content/Meshes/RightTouchForQuest2.uasset new file mode 100644 index 0000000000000000000000000000000000000000..512ca6fc2baaf12b9c15729e268ba9310d76b85a Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/RightTouchForQuest2.uasset differ diff --git a/Plugins/OculusXR/Content/Meshes/RightTouchForQuestRiftSController.fbx b/Plugins/OculusXR/Content/Meshes/RightTouchForQuestRiftSController.fbx new file mode 100644 index 0000000000000000000000000000000000000000..7a63b79a7751ea2afa1efdb390f9e69ee7d4aa7f Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/RightTouchForQuestRiftSController.fbx differ diff --git a/Plugins/OculusXR/Content/Meshes/RightTouchForQuestRiftSController.uasset b/Plugins/OculusXR/Content/Meshes/RightTouchForQuestRiftSController.uasset new file mode 100644 index 0000000000000000000000000000000000000000..0037fe13070dc84db34517656e36aecf2f2244e3 Binary files /dev/null and b/Plugins/OculusXR/Content/Meshes/RightTouchForQuestRiftSController.uasset differ diff --git a/Plugins/OculusXR/Content/OculusMR_GreenKey.uasset b/Plugins/OculusXR/Content/OculusMR_GreenKey.uasset new file mode 100644 index 0000000000000000000000000000000000000000..666bd1f12befa41ef0099537d4cfa93e934ddb26 Binary files /dev/null and b/Plugins/OculusXR/Content/OculusMR_GreenKey.uasset differ diff --git a/Plugins/OculusXR/Content/OculusModels.tps b/Plugins/OculusXR/Content/OculusModels.tps new file mode 100644 index 0000000000000000000000000000000000000000..0fac4c5f1af95b885f87b23888164132769fbd4f --- /dev/null +++ b/Plugins/OculusXR/Content/OculusModels.tps @@ -0,0 +1,13 @@ +<?xml version="1.0" encoding="utf-8"?> +<TpsData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <Name>Oculus Models</Name> + <Location>/Engine/Plugins/Runtime/Oculus/OculusVR/Content/</Location> + <Function>Provide our licensees with models that match actual physical Oculus devices.</Function> + <Eula>https://developer.oculus.com/licenses/art-1.0/</Eula> + <RedistributeTo> + <EndUserGroup>Licensees</EndUserGroup> + <EndUserGroup>Git</EndUserGroup> + <EndUserGroup>P4</EndUserGroup> + </RedistributeTo> + <LicenseFolder>/Engine/Source/ThirdParty/Licenses/OculusModels_License.txt</LicenseFolder> +</TpsData> \ No newline at end of file diff --git a/Plugins/OculusXR/Content/OculusModels_License.txt b/Plugins/OculusXR/Content/OculusModels_License.txt new file mode 100644 index 0000000000000000000000000000000000000000..4c170b2e5aa573b9d690fe902e013957aefda3ea --- /dev/null +++ b/Plugins/OculusXR/Content/OculusModels_License.txt @@ -0,0 +1,8 @@ +Art Attribution License 1.0 +Copyright © Facebook Technologies, LLC and its affiliates. All rights reserved. + +You may use these images solely for referring to the corresponding product in your video game or VR experience (including manuals for users). Otherwise, you may not use these images, or any trademarks, logos or other intellectual property owned by Facebook Technologies, LLC formerly known as Oculus VR, LLC (“Oculusâ€), including but not limited to use on merchandise or other product such as clothing, hats, or mugs. Do not use the Oculus images in a way that implies a partnership, sponsorship or endorsement; or features Oculus on materials associated with pornography, illegal activities, or other materials that violate Oculus Terms. + +THE IMAGES ARE PROVIDED TO YOU ON AN “AS IS†BASIS AND YOU ARE SOLELY RESPONSIBLE FOR YOUR USE OF THE IMAGES. OCULUS DISCLAIMS ALL WARRANTIES REGARDING THE IMAGES, INCLUDING WARRANTIES OF NON-INFRINGEMENT. OCULUS SHALL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL OR PUNITIVE DAMAGES ARISING FROM OR RELATED TO YOUR USE OF THE IMAGES. + +For the avoidance of doubt, this license shall not apply to the Oculus name, trademark or service mark, logo or design. \ No newline at end of file diff --git a/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchProBatteryIndicator.uasset b/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchProBatteryIndicator.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e6977d51b89690a75faf0cf70143e3eec664f654 Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchProBatteryIndicator.uasset differ diff --git a/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchPro_Color.uasset b/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchPro_Color.uasset new file mode 100644 index 0000000000000000000000000000000000000000..e28d27bcaed1f623ada088f2e599a4471b56ad9f Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchPro_Color.uasset differ diff --git a/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchPro_Normal.uasset b/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchPro_Normal.uasset new file mode 100644 index 0000000000000000000000000000000000000000..700cd0c1177ca8d00e3aa62b2084d857d524c37a Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/LeftMetaQuestTouchPro_Normal.uasset differ diff --git a/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchProBatteryIndicator.uasset b/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchProBatteryIndicator.uasset new file mode 100644 index 0000000000000000000000000000000000000000..ae5b0dd46b52e6d7899bf977bd85fe337a49908a Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchProBatteryIndicator.uasset differ diff --git a/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchPro_Color.uasset b/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchPro_Color.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7ec73c327101f339d51d0a394eab5102aaf36bb7 Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchPro_Color.uasset differ diff --git a/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchPro_Normal.uasset b/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchPro_Normal.uasset new file mode 100644 index 0000000000000000000000000000000000000000..5e3a130a6904fa20b0dbac5fab1b424b823ed664 Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/RightMetaQuestTouchPro_Normal.uasset differ diff --git a/Plugins/OculusXR/Content/Textures/TouchForQuest2Material_Roughness.uasset b/Plugins/OculusXR/Content/Textures/TouchForQuest2Material_Roughness.uasset new file mode 100644 index 0000000000000000000000000000000000000000..3f420a33a9a03e57de58449b47c6add4d6a4221d Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/TouchForQuest2Material_Roughness.uasset differ diff --git a/Plugins/OculusXR/Content/Textures/TouchForQuest2_Color.uasset b/Plugins/OculusXR/Content/Textures/TouchForQuest2_Color.uasset new file mode 100644 index 0000000000000000000000000000000000000000..4202bf820b5ed5612dbfda0e27428751fc5f9d1b Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/TouchForQuest2_Color.uasset differ diff --git a/Plugins/OculusXR/Content/Textures/TouchForQuestRiftSController_albedo.uasset b/Plugins/OculusXR/Content/Textures/TouchForQuestRiftSController_albedo.uasset new file mode 100644 index 0000000000000000000000000000000000000000..7260b503f468383b98b67041744d88b166a93b67 Binary files /dev/null and b/Plugins/OculusXR/Content/Textures/TouchForQuestRiftSController_albedo.uasset differ diff --git a/Plugins/OculusXR/OculusXR.uplugin b/Plugins/OculusXR/OculusXR.uplugin new file mode 100644 index 0000000000000000000000000000000000000000..6986573f73c7c89bc90d05d7ed7ef3b29a883fd7 --- /dev/null +++ b/Plugins/OculusXR/OculusXR.uplugin @@ -0,0 +1,117 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.81.0", + "FriendlyName": "Meta XR", + "Description": "Support for Meta Quest headsets and controllers", + "Category": "Virtual Reality", + "CreatedBy": "Meta Platforms, Inc.", + "CreatedByURL": "https://www.meta.com/", + "DocsURL": "https://developer.oculus.com/documentation/unreal/latest/concepts/unreal-engine/", + "MarketplaceURL": "com.epicgames.launcher://ue/marketplace/product/8313d8d7e7cf4e03a33e79eb757bccba", + "SupportURL": "https://forums.oculusvr.com/developer", + "EnabledByDefault": false, + "CanContainContent": true, + "IsBetaVersion": false, + "Installed": false, + "SupportedTargetPlatforms": [ + "Win64", + "Android" + ], + "Modules": [ + { + "Name": "OculusXRHMD", + "Type": "Runtime", + "LoadingPhase": "PostConfigInit", + "PlatformAllowList": [ + "Win64", + "Android" + ] + }, + { + "Name": "OculusXRInput", + "Type": "Runtime", + "LoadingPhase": "Default", + "PlatformAllowList": [ + "Win64", + "Android" + ] + }, + { + "Name": "OculusXRMR", + "Type": "Runtime", + "LoadingPhase": "PostEngineInit", + "PlatformAllowList": [ + "Win64", + "Android" + ] + }, + { + "Name": "OculusXROpenXRHMD", + "Type": "Runtime", + "LoadingPhase": "PostConfigInit", + "PlatformAllowList": [ + "Win64", + "Android" + ] + }, + { + "Name": "OculusXRAnchors", + "Type": "Runtime", + "LoadingPhase": "PostEngineInit", + "PlatformAllowList": [ + "Win64", + "Android" + ] + }, + { + "Name": "OculusXRScene", + "Type": "Runtime", + "LoadingPhase": "PostEngineInit", + "PlatformAllowList": [ + "Win64", + "Android" + ] + }, + { + "Name": "OculusXRMovement", + "Type": "Runtime", + "LoadingPhase": "PostConfigInit", + "WhitelistPlatforms": [ + "Win64", + "Android" + ] + }, + { + "Name": "OculusXREyeTracker", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ + "Win64", + "Android" + ] + }, + { + "Name": "OculusXREditor", + "Type": "Editor", + "LoadingPhase": "Default", + "PlatformAllowList": [ + "Win64" + ] + } + ], + "Plugins": [ + { + "Name": "ProceduralMeshComponent", + "Enabled": true + }, + { + "Name": "OpenXR", + "Enabled": true + }, + { + "Name": "AndroidPermission", + "Enabled": true + } + ] +} diff --git a/Plugins/OculusXR/Resources/ButtonIcon_80x.png b/Plugins/OculusXR/Resources/ButtonIcon_80x.png new file mode 100644 index 0000000000000000000000000000000000000000..235274aa095c3d693647fd78b39b4dff3d9ddf5b Binary files /dev/null and b/Plugins/OculusXR/Resources/ButtonIcon_80x.png differ diff --git a/Plugins/OculusXR/Resources/Icon128.png b/Plugins/OculusXR/Resources/Icon128.png new file mode 100644 index 0000000000000000000000000000000000000000..3044e3d7be6e90ef8ef4ed6e4b7ebcde1142dd04 Binary files /dev/null and b/Plugins/OculusXR/Resources/Icon128.png differ diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/OculusXRAnchors.Build.cs b/Plugins/OculusXR/Source/OculusXRAnchors/OculusXRAnchors.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..a9256d041b762a7c8a8687faaf8948e73b8b3737 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/OculusXRAnchors.Build.cs @@ -0,0 +1,31 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class OculusXRAnchors : ModuleRules + { + public OculusXRAnchors(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "OculusXRHMD", + "OVRPluginXR", + }); + + PrivateIncludePaths.AddRange( + new string[] { + // Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source + "OculusXRHMD/Private", + }); + + PublicIncludePaths.AddRange( + new string[] { + "Runtime/Engine/Classes/Components", + }); + } + } +} diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorBPFunctionLibrary.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorBPFunctionLibrary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..084a6bbd625fa3060a003802375db77b22c84662 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorBPFunctionLibrary.cpp @@ -0,0 +1,140 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRAnchorBPFunctionLibrary.h" +#include "OculusXRHMD.h" +#include "OculusXRSpatialAnchorComponent.h" +#include "OculusXRAnchorsPrivate.h" +#include "Kismet/BlueprintFunctionLibrary.h" + +AActor* UOculusXRAnchorBPFunctionLibrary::SpawnActorWithAnchorHandle(UObject* WorldContextObject, FOculusXRUInt64 Handle, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation Location, UClass* ActorClass, + AActor* Owner, APawn* Instigator, ESpawnActorCollisionHandlingMethod CollisionHandlingMethod) +{ + FActorSpawnParameters SpawnInfo; + SpawnInfo.Owner = Owner; + SpawnInfo.Instigator = Instigator; + SpawnInfo.ObjectFlags |= RF_Transient; + SpawnInfo.SpawnCollisionHandlingOverride = CollisionHandlingMethod; + + UWorld* World = GEngine->GetWorldFromContextObject(WorldContextObject, EGetWorldErrorMode::LogAndReturnNull); + if (World == nullptr) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid WorldContext Object for SpawnActorWithAnchorHandle.")); + return nullptr; + } + + AActor* NewSpatialAnchorActor = World->SpawnActor(ActorClass, nullptr, nullptr, SpawnInfo); + if (NewSpatialAnchorActor == nullptr) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to spawn Actor in SpawnActorWithAnchorHandle")); + return nullptr; + } + + UOculusXRSpatialAnchorComponent* SpatialAnchorComponent = NewSpatialAnchorActor->FindComponentByClass<UOculusXRSpatialAnchorComponent>(); + if (SpatialAnchorComponent == nullptr) + { + SpatialAnchorComponent = Cast<UOculusXRSpatialAnchorComponent>(NewSpatialAnchorActor->AddComponentByClass(UOculusXRSpatialAnchorComponent::StaticClass(), false, FTransform::Identity, false)); + } + + if (!IsValid(SpatialAnchorComponent)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to find or spawn Spatial Anchor component in SpawnActorWithAnchorHandle")); + return nullptr; + } + + SpatialAnchorComponent->SetHandle(Handle); + SpatialAnchorComponent->SetUUID(UUID); + SpatialAnchorComponent->SetStoredLocation(Location, true); + return NewSpatialAnchorActor; +} + +AActor* UOculusXRAnchorBPFunctionLibrary::SpawnActorWithAnchorQueryResults(UObject* WorldContextObject, const FOculusXRSpaceQueryResult& QueryResult, UClass* ActorClass, AActor* Owner, APawn* Instigator, ESpawnActorCollisionHandlingMethod CollisionHandlingMethod) +{ + return SpawnActorWithAnchorHandle(WorldContextObject, QueryResult.Space, QueryResult.UUID, QueryResult.Location, ActorClass, Owner, Instigator, CollisionHandlingMethod); +} + +bool UOculusXRAnchorBPFunctionLibrary::GetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool& bIsEnabled) +{ + UOculusXRAnchorComponent* AnchorComponent = Cast<UOculusXRAnchorComponent>(TargetActor->GetComponentByClass(UOculusXRAnchorComponent::StaticClass())); + + if (!IsValid(AnchorComponent)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Anchor Component provided to GetAnchorComponentStatus")); + bIsEnabled = false; + return false; + } + + bool bOutIsEnabled = false; + bool bIsChangePending = false; + + bool bDidCallStart = OculusXRAnchors::FOculusXRAnchors::GetAnchorComponentStatus(AnchorComponent, ComponentType, bOutIsEnabled, bIsChangePending); + if (!bDidCallStart) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start call to internal GetAnchorComponentStatus")); + bIsEnabled = false; + return false; + } + + bIsEnabled = bOutIsEnabled; + return bIsEnabled; +} + +bool UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(const FOculusXRUInt64& Handle, FTransform& OutTransform) +{ + OculusXRHMD::FOculusXRHMD* OutHMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD(); + if (!OutHMD) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot calculate anchor transform.")); + return false; + } + + ovrpTrackingOrigin ovrpOrigin = ovrpTrackingOrigin_EyeLevel; + const bool bTrackingOriginSuccess = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetTrackingOriginType2(&ovrpOrigin)); + if (!bTrackingOriginSuccess) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to get tracking origin, cannot calculate anchor transform.")); + return false; + } + + const ovrpUInt64 ovrpSpace = Handle.GetValue(); + ovrpPosef ovrpPose; + + const bool bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().LocateSpace(&ovrpPose, &ovrpSpace, ovrpOrigin)); + if (bSuccess) + { + OculusXRHMD::FPose Pose; + OutHMD->ConvertPose(ovrpPose, Pose); + const FTransform trackingToWorld = OutHMD->GetLastTrackingToWorld(); + + OutTransform.SetLocation(trackingToWorld.TransformPosition(Pose.Position)); + OutTransform.SetRotation(FRotator(trackingToWorld.TransformRotation(FQuat(Pose.Orientation))).Quaternion()); + } + + return bSuccess; +} + +FString UOculusXRAnchorBPFunctionLibrary::AnchorHandleToString(const FOculusXRUInt64 Value) +{ + return FString::Printf(TEXT("%llu"), Value.Value); +} + +FString UOculusXRAnchorBPFunctionLibrary::AnchorUUIDToString(const FOculusXRUUID& Value) +{ + return Value.ToString(); +} + +FOculusXRUUID UOculusXRAnchorBPFunctionLibrary::StringToAnchorUUID(const FString& Value) +{ + // Static size for the max length of the string, two chars per hex digit, 16 digits. + checkf(Value.Len() == 32, TEXT("'%s' is not a valid UUID"), *Value); + + ovrpUuid newID; + HexToBytes(Value, newID.data); + + return FOculusXRUUID(newID.data); +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponent.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3f391f2758957d559c1324971266222c0f19bf45 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorComponent.cpp @@ -0,0 +1,207 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRAnchorComponent.h" +#include "OculusXRAnchors.h" +#include "OculusXRHMD.h" +#include "OculusXRAnchorBPFunctionLibrary.h" +#include "OculusXRAnchorsPrivate.h" +#include "GameFramework/PlayerController.h" + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) +static TAutoConsoleVariable<int32> CVarOculusXRVerboseAnchorDebugXR( + TEXT("ovr.OculusXRVerboseAnchorDebug"), + 0, + TEXT("Enables or disables verbose logging for Oculus anchors.\n") + TEXT("<=0: disabled (no printing)\n") + TEXT(" 1: enabled (verbose logging)\n")); +#endif + +UOculusXRAnchorComponent::UOculusXRAnchorComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , bUpdateHeadSpaceTransform(true) + , AnchorHandle(0) + , StorageLocations(0) +{ + AnchorHandle = 0; + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + PrimaryComponentTick.TickGroup = TG_PostUpdateWork; +} + +void UOculusXRAnchorComponent::BeginPlay() +{ + Super::BeginPlay(); + + UWorld* World = GetWorld(); + if (IsValid(World)) + { + APlayerController* PlayerController = World->GetFirstPlayerController(); + if (IsValid(PlayerController)) + { + PlayerCameraManager = PlayerController->PlayerCameraManager; + } + } +} + +void UOculusXRAnchorComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + UpdateAnchorTransform(); +} + +void UOculusXRAnchorComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + Super::EndPlay(EndPlayReason); + + if (HasValidHandle()) + { + OculusXRAnchors::FOculusXRAnchors::DestroyAnchor(AnchorHandle.GetValue()); + } +} + +FOculusXRUInt64 UOculusXRAnchorComponent::GetHandle() const +{ + return AnchorHandle; +} + +void UOculusXRAnchorComponent::SetHandle(FOculusXRUInt64 Handle) +{ + AnchorHandle = Handle; +} + +bool UOculusXRAnchorComponent::HasValidHandle() const +{ + return AnchorHandle != FOculusXRUInt64(0); +} + +FOculusXRUUID UOculusXRAnchorComponent::GetUUID() const +{ + return AnchorUUID; +} + +void UOculusXRAnchorComponent::SetUUID(FOculusXRUUID NewUUID) +{ + if (AnchorUUID.IsValidUUID()) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor component already has valid UUID, cannot re-assign a new UUID. Component: %s -- Space: %llu -- UUID: %s"), + *GetName(), AnchorHandle, *AnchorUUID.ToString()); + return; + } + + if (!NewUUID.IsValidUUID()) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("New UUID provided to component is invalid, cannot assign. Component: %s -- Space: %llu"), *GetName(), AnchorHandle); + return; + } + + UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Assigned new Oculus UUID: %s"), *NewUUID.ToString()); + + AnchorUUID = NewUUID; +} + +bool UOculusXRAnchorComponent::IsStoredAtLocation(EOculusXRSpaceStorageLocation Location) const +{ + UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Anchor UUID: %s - Saved Local: %d"), *GetUUID().ToString(), StorageLocations & static_cast<int32>(EOculusXRSpaceStorageLocation::Local)); + + return (StorageLocations & static_cast<int32>(Location)) > 0; +} + +void UOculusXRAnchorComponent::SetStoredLocation(EOculusXRSpaceStorageLocation Location, bool Stored) +{ + if (Stored) + { + StorageLocations |= static_cast<int32>(Location); + } + else + { + StorageLocations = StorageLocations & ~static_cast<int32>(Location); + } + + UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Anchor UUID: %s - Saved Local: %d"), *GetUUID().ToString(), StorageLocations & static_cast<int32>(EOculusXRSpaceStorageLocation::Local)); +} + +bool UOculusXRAnchorComponent::IsSaved() const +{ + return StorageLocations > 0; +} + +void UOculusXRAnchorComponent::UpdateAnchorTransform() const +{ + if (GetWorld() == nullptr) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve World Context")); + return; + } + + AActor* Parent = GetOwner(); + if (Parent) + { + if(AnchorHandle.Value) + { + FTransform OutTransform; + if(UOculusXRAnchorBPFunctionLibrary::GetAnchorTransformByHandle(AnchorHandle, OutTransform)) + { +#if WITH_EDITOR + // Link only head-space transform update + if (bUpdateHeadSpaceTransform && PlayerCameraManager != nullptr) + { + FTransform MainCameraTransform; + MainCameraTransform.SetLocation(PlayerCameraManager->GetCameraLocation()); + MainCameraTransform.SetRotation(FQuat(PlayerCameraManager->GetCameraRotation())); + + if (!ToWorldSpacePose(MainCameraTransform, OutTransform)) + { + UE_LOG(LogOculusXRAnchors, Display, TEXT("Was not able to transform anchor to world space pose")); + } + } +#endif + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (CVarOculusXRVerboseAnchorDebugXR.GetValueOnGameThread() > 0) + { + UE_LOG(LogOculusXRAnchors, Display, TEXT("UpdateAnchor Pos %s"), *OutTransform.GetLocation().ToString()); + UE_LOG(LogOculusXRAnchors, Display, TEXT("UpdateAnchor Rot %s"), *OutTransform.GetRotation().ToString()); + } +#endif + } + + Parent->SetActorLocationAndRotation(OutTransform.GetLocation(), OutTransform.GetRotation(), false, 0, ETeleportType::ResetPhysics); + } + } +} + +bool UOculusXRAnchorComponent::ToWorldSpacePose(FTransform CameraTransform, FTransform& OutTrackingSpaceTransform) const +{ + OculusXRHMD::FOculusXRHMD* OculusXRHMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD(); + if (!OculusXRHMD) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot calculate anchor world space pose.")); + return false; + } + + OculusXRHMD::FPose MainCameraPose(CameraTransform.GetRotation(), CameraTransform.GetLocation()); + OculusXRHMD::FPose TrackingSpacePose(OutTrackingSpaceTransform.GetRotation(), OutTrackingSpaceTransform.GetLocation()); + + FVector OutHeadPosition; + FQuat OutHeadOrientation; + const bool bGetPose = OculusXRHMD->GetCurrentPose(OculusXRHMD->HMDDeviceId, OutHeadOrientation, OutHeadPosition); + if (!bGetPose) return false; + + OculusXRHMD::FPose HeadPose(OutHeadOrientation, OutHeadPosition); + + OculusXRHMD::FPose poseInHeadSpace = HeadPose.Inverse() * TrackingSpacePose; + + // To world space pose + const OculusXRHMD::FPose WorldTrackingSpacePose = MainCameraPose * poseInHeadSpace; + + OutTrackingSpaceTransform.SetLocation(WorldTrackingSpacePose.Position); + OutTrackingSpaceTransform.SetRotation(WorldTrackingSpacePose.Orientation); + + return true; +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorDelegates.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorDelegates.cpp new file mode 100644 index 0000000000000000000000000000000000000000..62a72f561d0806ec03068a48d46c7422b3800d20 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorDelegates.cpp @@ -0,0 +1,25 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRAnchorDelegates.h" + +FOculusXRAnchorEventDelegates::FOculusXRSpatialAnchorCreateCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete; + +FOculusXRAnchorEventDelegates::FOculusXRSpaceSetComponentStatusCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete; + +FOculusXRAnchorEventDelegates::FOculusXRSpaceQueryResultsDelegate FOculusXRAnchorEventDelegates::OculusSpaceQueryResults; + +FOculusXRAnchorEventDelegates::FOculusXRSpaceQueryResultDelegate FOculusXRAnchorEventDelegates::OculusSpaceQueryResult; + +FOculusXRAnchorEventDelegates::FOculusXRSpaceQueryCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete; + +FOculusXRAnchorEventDelegates::FOculusXRSpaceSaveCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete; + +FOculusXRAnchorEventDelegates::FOculusXRSpaceEraseCompleteDelegate FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete; + +FOculusXRAnchorEventDelegates::FOculusXRSceneCaptureCompleteDelegate FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorLatentActions.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorLatentActions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..06bdccdef87778b2235d0f1da48ad7057cd02c08 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorLatentActions.cpp @@ -0,0 +1,305 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRAnchorLatentActions.h" +#include "OculusXRAnchorsPrivate.h" +#include "OculusXRHMD.h" + +// +// Create Spatial Anchor +// +void UOculusXRAsyncAction_CreateSpatialAnchor::Activate() +{ + if (!IsValid(TargetActor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to CreateSpatialAnchor latent action.")); + + Failure.Broadcast(); + return; + } + + // Bind the callback delegate and create the anchor + bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::CreateSpatialAnchor( + AnchorTransform, + TargetActor, + FOculusXRSpatialAnchorCreateDelegate::CreateUObject(this, &UOculusXRAsyncAction_CreateSpatialAnchor::HandleCreateComplete) + ); + + if (!bStartedAsync) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for CreateSpatialAnchor latent action.")); + Failure.Broadcast(); + } +} + +UOculusXRAsyncAction_CreateSpatialAnchor* UOculusXRAsyncAction_CreateSpatialAnchor::OculusXRAsyncCreateSpatialAnchor(AActor* TargetActor, const FTransform& AnchorTransform) +{ + UOculusXRAsyncAction_CreateSpatialAnchor* Action = NewObject<UOculusXRAsyncAction_CreateSpatialAnchor>(); + Action->TargetActor = TargetActor; + Action->AnchorTransform = AnchorTransform; + Action->RegisterWithGameInstance(TargetActor->GetWorld()); + + return Action; +} + +void UOculusXRAsyncAction_CreateSpatialAnchor::HandleCreateComplete(bool CreateSuccess, UOculusXRAnchorComponent* Anchor) +{ + if (CreateSuccess) + { + Success.Broadcast(Anchor); + } + else + { + Failure.Broadcast(); + } + + SetReadyToDestroy(); +} + + +// +// Erase Space +// +void UOculusXRAsyncAction_EraseAnchor::Activate() +{ + if (!IsValid(TargetActor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to EraseSpace latent action.")); + + Failure.Broadcast(); + return; + } + + UOculusXRAnchorComponent* AnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>(); + if (AnchorComponent == nullptr) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("No anchor on actor in EraseSpace latent action.")); + + Failure.Broadcast(); + return; + } + + // Bind the callback delegate and start delete + bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::EraseAnchor( + AnchorComponent, + FOculusXRAnchorEraseDelegate::CreateUObject(this, &UOculusXRAsyncAction_EraseAnchor::HandleEraseAnchorComplete) + ); + + if (!bStartedAsync) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for EraseSpace latent action.")); + + Failure.Broadcast(); + } +} + +UOculusXRAsyncAction_EraseAnchor* UOculusXRAsyncAction_EraseAnchor::OculusXRAsyncEraseAnchor(AActor* TargetActor) +{ + UOculusXRAsyncAction_EraseAnchor* Action = NewObject<UOculusXRAsyncAction_EraseAnchor>(); + Action->TargetActor = TargetActor; + + if (IsValid(TargetActor)) + { + Action->RegisterWithGameInstance(TargetActor->GetWorld()); + } + + return Action; +} + +void UOculusXRAsyncAction_EraseAnchor::HandleEraseAnchorComplete(bool EraseSuccess, FOculusXRUUID UUID) +{ + if (EraseSuccess) + { + Success.Broadcast(TargetActor, UUID); + } + else + { + Failure.Broadcast(); + } + + SetReadyToDestroy(); +} + + +// +// Save Space +// +void UOculusXRAsyncAction_SaveAnchor::Activate() +{ + if (!IsValid(TargetActor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to SaveSpace latent action.")); + + Failure.Broadcast(); + return; + } + + UOculusXRAnchorComponent* AnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>(); + if (AnchorComponent == nullptr) + { + Failure.Broadcast(); + return; + } + + UE_LOG(LogOculusXRAnchors, Log, TEXT("Attempting to save anchor: %s to location %s"), IsValid(AnchorComponent) ? *AnchorComponent->GetName() : TEXT("INVALID ANCHOR"), *UEnum::GetValueAsString(StorageLocation)); + + // Bind the callback delegate and start save + bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SaveAnchor( + AnchorComponent, + StorageLocation, + FOculusXRAnchorSaveDelegate::CreateUObject(this, &UOculusXRAsyncAction_SaveAnchor::HandleSaveAnchorComplete)); + + if (!bStartedAsync) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SaveSpace latent action.")); + Failure.Broadcast(); + } +} + +UOculusXRAsyncAction_SaveAnchor* UOculusXRAsyncAction_SaveAnchor::OculusXRAsyncSaveAnchor(AActor* TargetActor, EOculusXRSpaceStorageLocation StorageLocation) +{ + UOculusXRAsyncAction_SaveAnchor* Action = NewObject<UOculusXRAsyncAction_SaveAnchor>(); + Action->TargetActor = TargetActor; + Action->StorageLocation = StorageLocation; + Action->RegisterWithGameInstance(TargetActor->GetWorld()); + + return Action; +} + +void UOculusXRAsyncAction_SaveAnchor::HandleSaveAnchorComplete(bool SaveSuccess, UOculusXRAnchorComponent* Anchor) +{ + if (SaveSuccess) + { + Success.Broadcast(Anchor); + } + else + { + Failure.Broadcast(); + } + + SetReadyToDestroy(); +} + + +// +// Query Spaces +// +void UOculusXRAsyncAction_QueryAnchors::Activate() +{ + // Bind the callback delegates and start the query + bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::QueryAnchorsAdvanced( + QueryInfo, + FOculusXRAnchorQueryDelegate::CreateUObject(this, &UOculusXRAsyncAction_QueryAnchors::HandleQueryAnchorsResults)); + + if (!bStartedAsync) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for QuerySpaces latent action.")); + + Failure.Broadcast(); + } +} + +UOculusXRAsyncAction_QueryAnchors* UOculusXRAsyncAction_QueryAnchors::OculusXRAsyncQueryAnchors(EOculusXRSpaceStorageLocation Location, const TArray<FOculusXRUUID>& UUIDs, int32 MaxAnchors) +{ + FOculusXRSpaceQueryInfo QueryInfo; + QueryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByIds; + QueryInfo.IDFilter = UUIDs; + QueryInfo.Location = Location; + QueryInfo.MaxQuerySpaces = MaxAnchors; + + UOculusXRAsyncAction_QueryAnchors* Action = NewObject<UOculusXRAsyncAction_QueryAnchors>(); + Action->QueryInfo = QueryInfo; + + return Action; +} + +UOculusXRAsyncAction_QueryAnchors* UOculusXRAsyncAction_QueryAnchors::OculusXRAsyncQueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo) +{ + UOculusXRAsyncAction_QueryAnchors* Action = NewObject<UOculusXRAsyncAction_QueryAnchors>(); + Action->QueryInfo = QueryInfo; + + return Action; +} + +void UOculusXRAsyncAction_QueryAnchors::HandleQueryAnchorsResults(bool QuerySuccess, const TArray<FOculusXRSpaceQueryResult>& Results) +{ + QueryResults = Results; + + if (QuerySuccess) + { + Success.Broadcast(QueryResults); + } + else + { + Failure.Broadcast(); + } + + SetReadyToDestroy(); +} + + +// +// Set Component Status +// +void UOculusXRAsyncAction_SetAnchorComponentStatus::Activate() +{ + if (!IsValid(TargetActor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid Target Actor passed to SetComponentStatus latent action.")); + + Failure.Broadcast(); + return; + } + + UOculusXRAnchorComponent* AnchorComponent = TargetActor->FindComponentByClass<UOculusXRAnchorComponent>(); + if (AnchorComponent == nullptr) + { + Failure.Broadcast(); + return; + } + + // Bind the callback delegates and start the query + bool bStartedAsync = OculusXRAnchors::FOculusXRAnchors::SetAnchorComponentStatus( + AnchorComponent, + ComponentType, + bEnabled, + 0, + FOculusXRAnchorSetComponentStatusDelegate::CreateUObject(this, &UOculusXRAsyncAction_SetAnchorComponentStatus::HandleSetComponentStatusComplete)); + + if (!bStartedAsync) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async OVR Plugin call for SetComponentStatus latent action.")); + + Failure.Broadcast(); + } +} + +UOculusXRAsyncAction_SetAnchorComponentStatus* UOculusXRAsyncAction_SetAnchorComponentStatus::OculusXRAsyncSetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool bEnabled) +{ + UOculusXRAsyncAction_SetAnchorComponentStatus* Action = NewObject<UOculusXRAsyncAction_SetAnchorComponentStatus>(); + Action->TargetActor = TargetActor; + Action->ComponentType = ComponentType; + Action->bEnabled = bEnabled; + Action->RegisterWithGameInstance(TargetActor); + + return Action; +} + +void UOculusXRAsyncAction_SetAnchorComponentStatus::HandleSetComponentStatusComplete(bool SetStatusSuccess, UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool bResultEnabled) +{ + if (SetStatusSuccess) + { + Success.Broadcast(Anchor, SpaceComponentType, bResultEnabled); + } + else + { + Failure.Broadcast(); + } + + SetReadyToDestroy(); +} diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c7e8d874ea85c0596a68ee116102ad90a7d6661f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.cpp @@ -0,0 +1,715 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRAnchorManager.h" + +#include <vector> + +#include "OculusXRHMD.h" +#include "OculusXRAnchorsModule.h" +#include "OculusXRAnchorDelegates.h" +#include "OculusXRAnchorBPFunctionLibrary.h" + +namespace OculusXRAnchors +{ + OculusXRHMD::FOculusXRHMD* GetHMD(bool& OutSuccessful) + { + OculusXRHMD::FOculusXRHMD* OutHMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD(); + if (!OutHMD) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD")); + OutSuccessful = false; + } + + OutSuccessful = true; + + return OutHMD; + } + + ovrpUuid ConvertFOculusXRUUIDtoOvrpUuid(const FOculusXRUUID& UUID) + { + ovrpUuid Result; + FMemory::Memcpy(Result.data, UUID.UUIDBytes); + + return Result; + } + + ovrpSpaceQueryInfo ConvertToOVRPSpaceQueryInfo(const FOculusXRSpaceQueryInfo& UEQueryInfo) + { + static const int32 MaxIdsInFilter = 1024; + static const int32 MaxComponentTypesInFilter = 16; + + ovrpSpaceQueryInfo Result; + Result.queryType = ovrpSpaceQueryType_Action; + Result.actionType = ovrpSpaceQueryActionType_Load; + + Result.maxQuerySpaces = UEQueryInfo.MaxQuerySpaces; + Result.timeout = static_cast<double>(UEQueryInfo.Timeout); // Prevent compiler warnings, though there is a possible loss of data here. + + switch (UEQueryInfo.Location) + { + case EOculusXRSpaceStorageLocation::Invalid: + Result.location = ovrpSpaceStorageLocation_Invalid; + break; + case EOculusXRSpaceStorageLocation::Local: + Result.location = ovrpSpaceStorageLocation_Local; + break; + } + + switch (UEQueryInfo.FilterType) + { + case EOculusXRSpaceQueryFilterType::None: + Result.filterType = ovrpSpaceQueryFilterType_None; + break; + case EOculusXRSpaceQueryFilterType::FilterByIds: + Result.filterType = ovrpSpaceQueryFilterType_Ids; + break; + case EOculusXRSpaceQueryFilterType::FilterByComponentType: + Result.filterType = ovrpSpaceQueryFilterType_Components; + break; + } + + Result.IdInfo.numIds = FMath::Min(MaxIdsInFilter, UEQueryInfo.IDFilter.Num()); + for (int i = 0; i < Result.IdInfo.numIds; ++i) + { + ovrpUuid OvrUuid = ConvertFOculusXRUUIDtoOvrpUuid(UEQueryInfo.IDFilter[i]); + Result.IdInfo.ids[i] = OvrUuid; + } + + Result.componentsInfo.numComponents = FMath::Min(MaxComponentTypesInFilter, UEQueryInfo.ComponentFilter.Num()); + for (int i = 0; i < Result.componentsInfo.numComponents; ++i) + { + ovrpSpaceComponentType componentType = ovrpSpaceComponentType::ovrpSpaceComponentType_Max; + + switch (UEQueryInfo.ComponentFilter[i]) + { + case EOculusXRSpaceComponentType::Locatable: + componentType = ovrpSpaceComponentType_Locatable; + break; + case EOculusXRSpaceComponentType::Storable: + componentType = ovrpSpaceComponentType_Storable; + break; + case EOculusXRSpaceComponentType::ScenePlane: + componentType = ovrpSpaceComponentType_Bounded2D; + break; + case EOculusXRSpaceComponentType::SceneVolume: + componentType = ovrpSpaceComponentType_Bounded3D; + break; + case EOculusXRSpaceComponentType::SemanticClassification: + componentType = ovrpSpaceComponentType_SemanticLabels; + break; + case EOculusXRSpaceComponentType::RoomLayout: + componentType = ovrpSpaceComponentType_RoomLayout; + break; + case EOculusXRSpaceComponentType::SpaceContainer: + componentType = ovrpSpaceComponentType_SpaceContainer; + break; + } + + Result.componentsInfo.components[i] = componentType; + } + + return Result; + } + + template<typename T> + void GetEventData(ovrpEventDataBuffer& Buffer, T& OutEventData) + { + unsigned char* BufData = Buffer.EventData; + BufData -= sizeof(uint64); //correct offset + + memcpy(&OutEventData, BufData, sizeof(T)); + } + + ovrpSpaceComponentType ConvertToOvrpComponentType(const EOculusXRSpaceComponentType ComponentType) + { + ovrpSpaceComponentType ovrpType = ovrpSpaceComponentType_Max; + switch (ComponentType) + { + case EOculusXRSpaceComponentType::Locatable: + ovrpType = ovrpSpaceComponentType_Locatable; + break; + case EOculusXRSpaceComponentType::Storable: + ovrpType = ovrpSpaceComponentType_Storable; + break; + case EOculusXRSpaceComponentType::ScenePlane: + ovrpType = ovrpSpaceComponentType_Bounded2D; + break; + case EOculusXRSpaceComponentType::SceneVolume: + ovrpType = ovrpSpaceComponentType_Bounded3D; + break; + case EOculusXRSpaceComponentType::SemanticClassification: + ovrpType = ovrpSpaceComponentType_SemanticLabels; + break; + case EOculusXRSpaceComponentType::RoomLayout: + ovrpType = ovrpSpaceComponentType_RoomLayout; + break; + case EOculusXRSpaceComponentType::SpaceContainer: + ovrpType = ovrpSpaceComponentType_SpaceContainer; + break; + default:; + } + + return ovrpType; + } + + EOculusXRSpaceComponentType ConvertToUe4ComponentType(const ovrpSpaceComponentType ComponentType) + { + EOculusXRSpaceComponentType ue4ComponentType = EOculusXRSpaceComponentType::Undefined; + switch (ComponentType) + { + case ovrpSpaceComponentType_Locatable: + ue4ComponentType = EOculusXRSpaceComponentType::Locatable; + break; + case ovrpSpaceComponentType_Storable: + ue4ComponentType = EOculusXRSpaceComponentType::Storable; + break; + case ovrpSpaceComponentType_Bounded2D: + ue4ComponentType = EOculusXRSpaceComponentType::ScenePlane; + break; + case ovrpSpaceComponentType_Bounded3D: + ue4ComponentType = EOculusXRSpaceComponentType::SceneVolume; + break; + case ovrpSpaceComponentType_SemanticLabels: + ue4ComponentType = EOculusXRSpaceComponentType::SemanticClassification; + break; + case ovrpSpaceComponentType_RoomLayout: + ue4ComponentType = EOculusXRSpaceComponentType::RoomLayout; + break; + case ovrpSpaceComponentType_SpaceContainer: + ue4ComponentType = EOculusXRSpaceComponentType::SpaceContainer; + break; + default:; + } + + return ue4ComponentType; + } + + void FOculusXRAnchorManager::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult) + { + ovrpEventDataBuffer& buf = *EventDataBuffer; + + switch (buf.EventType) { + case ovrpEventType_None: break; + case ovrpEventType_SpatialAnchorCreateComplete: + { + ovrpEventDataSpatialAnchorCreateComplete AnchorCreateEvent; + GetEventData(buf, AnchorCreateEvent); + + const FOculusXRUInt64 RequestId(AnchorCreateEvent.requestId); + const FOculusXRUInt64 Space(AnchorCreateEvent.space); + const FOculusXRUUID BPUUID(AnchorCreateEvent.uuid.data); + + FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete.Broadcast(RequestId, AnchorCreateEvent.result, Space, BPUUID); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpatialAnchorCreateComplete Request ID: %llu -- Space: %llu -- UUID: %s -- Result: %d"), + RequestId.GetValue(), + Space.GetValue(), + *BPUUID.ToString(), + AnchorCreateEvent.result + ); + + break; + } + case ovrpEventType_SpaceSetComponentStatusComplete: + { + ovrpEventDataSpaceSetStatusComplete SetStatusEvent; + GetEventData(buf, SetStatusEvent); + + //translate to BP types + const FOculusXRUInt64 RequestId(SetStatusEvent.requestId); + const FOculusXRUInt64 Space(SetStatusEvent.space); + EOculusXRSpaceComponentType BPSpaceComponentType = ConvertToUe4ComponentType(SetStatusEvent.componentType); + const FOculusXRUUID BPUUID(SetStatusEvent.uuid.data); + const bool bEnabled = (SetStatusEvent.enabled == ovrpBool_True); + + FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete.Broadcast( + RequestId, + SetStatusEvent.result, + Space, + BPUUID, + BPSpaceComponentType, + bEnabled + ); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceSetComponentStatusComplete Request ID: %llu -- Type: %d -- Enabled: %d -- Space: %llu -- Result: %d"), + SetStatusEvent.requestId, + SetStatusEvent.componentType, + SetStatusEvent.enabled, + SetStatusEvent.space, + SetStatusEvent.result + ); + + break; + } + case ovrpEventType_SpaceQueryResults: + { + ovrpEventSpaceQueryResults QueryEvent; + GetEventData(buf, QueryEvent); + + const FOculusXRUInt64 RequestId(QueryEvent.requestId); + + FOculusXRAnchorEventDelegates::OculusSpaceQueryResults.Broadcast(RequestId); + + ovrpUInt32 ovrpOutCapacity = 0; + + //first get capacity + const bool bGetCapacityResult = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceQueryResults( + &QueryEvent.requestId, + 0, + &ovrpOutCapacity, + nullptr)); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceQueryResults Request ID: %llu -- Capacity: %d -- Result: %d"), QueryEvent.requestId, ovrpOutCapacity, bGetCapacityResult); + + std::vector<ovrpSpaceQueryResult> ovrpResults(ovrpOutCapacity); + + // Get Query Data + const bool bGetQueryDataResult = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().RetrieveSpaceQueryResults( + &QueryEvent.requestId, + ovrpResults.size(), + &ovrpOutCapacity, + ovrpResults.data())); + + for(auto queryResultElement: ovrpResults) + { + UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceQueryResult Space: %llu -- Result: %d"), queryResultElement.space, bGetQueryDataResult); + + //translate types + FOculusXRUInt64 Space(queryResultElement.space); + FOculusXRUUID BPUUID(queryResultElement.uuid.data); + FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.Broadcast(RequestId, Space, BPUUID); + } + + break; + } + case ovrpEventType_SpaceQueryComplete: + { + ovrpEventSpaceQueryComplete QueryCompleteEvent; + GetEventData(buf, QueryCompleteEvent); + + //translate to BP types + const FOculusXRUInt64 RequestId(QueryCompleteEvent.requestId); + const bool bSucceeded = QueryCompleteEvent.result >= 0; + + FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.Broadcast(RequestId, bSucceeded); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceQueryComplete Request ID: %llu -- Result: %d"), QueryCompleteEvent.requestId, QueryCompleteEvent.result); + + break; + } + case ovrpEventType_SpaceSaveComplete: + { + ovrpEventSpaceStorageSaveResult StorageResult; + GetEventData(buf, StorageResult); + + //translate to BP types + const FOculusXRUUID uuid(StorageResult.uuid.data); + const FOculusXRUInt64 FSpace(StorageResult.space); + const FOculusXRUInt64 FRequest(StorageResult.requestId); + const bool bResult = StorageResult.result >= 0; + + FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete.Broadcast(FRequest, FSpace, bResult, StorageResult.result, uuid); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceSaveComplete Request ID: %llu -- Space: %llu -- Result: %d"), StorageResult.requestId, StorageResult.space, StorageResult.result); + + break; + } + + case ovrpEventType_SpaceEraseComplete: + { + ovrpEventSpaceStorageEraseResult SpaceEraseEvent; + GetEventData(buf, SpaceEraseEvent); + + //translate to BP types + const FOculusXRUUID uuid(SpaceEraseEvent.uuid.data); + const FOculusXRUInt64 FRequestId(SpaceEraseEvent.requestId); + const FOculusXRUInt64 FResult(SpaceEraseEvent.result); + const EOculusXRSpaceStorageLocation BPLocation = (SpaceEraseEvent.location == ovrpSpaceStorageLocation_Local) ? EOculusXRSpaceStorageLocation::Local : EOculusXRSpaceStorageLocation::Invalid; + + UE_LOG(LogOculusXRAnchors, Log, TEXT("ovrpEventType_SpaceEraseComplete Request ID: %llu -- Result: %d -- UUID: %s"), SpaceEraseEvent.requestId, SpaceEraseEvent.result, *UOculusXRAnchorBPFunctionLibrary::AnchorUUIDToString(SpaceEraseEvent.uuid.data)); + + FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete.Broadcast(FRequestId,FResult.Value,uuid,BPLocation); + break; + } + + default: + { + EventPollResult = false; + break; + } + } + + EventPollResult = true; + } + + /** + * @brief Creates a spatial anchor + * @param InTransform The transform of the object + * @param OutRequestId the handle of the spatial anchor created + * @return True if the async call started successfully + */ + bool FOculusXRAnchorManager::CreateAnchor(const FTransform& InTransform, uint64 &OutRequestId, const FTransform& CameraTransform) + { + bool bValidHMD; + OculusXRHMD::FOculusXRHMD* HMD = GetHMD(bValidHMD); + if (!bValidHMD) + { + return false; + } + + ovrpTrackingOrigin TrackingOriginType; + ovrpPosef Posef; + double Time = 0; + + const FTransform TrackingToWorld = HMD->GetLastTrackingToWorld(); + + // convert to tracking space + const FQuat TrackingSpaceOrientation = TrackingToWorld.Inverse().TransformRotation(InTransform.Rotator().Quaternion()); + const FVector TrackingSpacePosition = TrackingToWorld.Inverse().TransformPosition(InTransform.GetLocation()); + + const OculusXRHMD::FPose TrackingSpacePose(TrackingSpaceOrientation, TrackingSpacePosition); + +#if WITH_EDITOR + // Link only head space position update + FVector OutHeadPosition; + FQuat OutHeadOrientation; + const bool bGetPose = HMD->GetCurrentPose(HMD->HMDDeviceId, OutHeadOrientation, OutHeadPosition); + if (!bGetPose) + { + return false; + } + + OculusXRHMD::FPose HeadPose(OutHeadOrientation, OutHeadPosition); + + OculusXRHMD::FPose MainCameraPose(CameraTransform.GetRotation(), CameraTransform.GetLocation()); + OculusXRHMD::FPose PoseInHeadSpace = MainCameraPose.Inverse() * TrackingSpacePose; + + // To world space pose + OculusXRHMD::FPose WorldPose = HeadPose * PoseInHeadSpace; + + const bool bConverted = HMD->ConvertPose(WorldPose, Posef); +#else + const bool bConverted = HMD->ConvertPose(TrackingSpacePose, Posef); +#endif + + if (!bConverted) + { + return false; + } + + FOculusXRHMDModule::GetPluginWrapper().GetTrackingOriginType2(&TrackingOriginType); + FOculusXRHMDModule::GetPluginWrapper().GetTimeInSeconds(&Time); + + const ovrpSpatialAnchorCreateInfo SpatialAnchorCreateInfo = { + TrackingOriginType, + Posef, + Time + }; + + const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().CreateSpatialAnchor(&SpatialAnchorCreateInfo, &OutRequestId); + if(Result == ovrpFailure) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusXRHMD::CreateAnchor failed")); + return false; + } + + UE_LOG(LogOculusXRAnchors, Log, TEXT("CreateAnchor Request ID: %llu"), OutRequestId); + + return true; + } + + /** + * @brief Destroys the given space; + * @param Space The space handle. + * @return True if the space was destroyed successfully. + */ + bool FOculusXRAnchorManager::DestroySpace(uint64 Space) + { + const ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().DestroySpace(static_cast<ovrpSpace*>(&Space)); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("DestroySpace Space ID: %llu"), Space); + + return (Result != ovrpFailure); + } + + /** + * @brief Sets the given component status on an anchor + * @param Space The space handle + * @param SpaceComponentType The component type to change + * @param Enable true or false + * @param Timeout Timeout for the command + * @param OutRequestId The requestId returned from the system + * @return True if the async call started successfully + */ + bool FOculusXRAnchorManager::SetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, uint64& OutRequestId) + { + ovrpSpaceComponentType ovrpType = ConvertToOvrpComponentType(SpaceComponentType); + ovrpUInt64 OvrpOutRequestId = 0; + + const ovrpUInt64 OVRPSpace = Space; + const bool bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().SetSpaceComponentStatus( + &OVRPSpace, + ovrpType, + Enable, + Timeout, + &OvrpOutRequestId)); + + memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64)); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("SetSpaceComponentStatus Request ID: %llu"), OutRequestId); + + return bSuccess; + } + + /** + * @brief + * @param Space The space handle for the space to determine the component status on + * @param SpaceComponentType The space component type + * @param OutEnabled true if the component is enabled + * @param OutChangePending true if there are pending changes + * @return True if the async call started successfully + */ + bool FOculusXRAnchorManager::GetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending) + { + const ovrpUInt64 OVRPSpace = Space; + ovrpBool OutOvrpEnabled = ovrpBool_False;; + ovrpBool OutOvrpChangePending = ovrpBool_False; + + ovrpSpaceComponentType ovrpType = ConvertToOvrpComponentType(SpaceComponentType); + + const bool bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetSpaceComponentStatus( + &OVRPSpace, + ovrpType, + &OutOvrpEnabled, + &OutOvrpChangePending + )); + + OutEnabled = (OutOvrpEnabled == ovrpBool_True); + OutChangePending = (OutOvrpChangePending == ovrpBool_True); + + return bSuccess; + } + + /** + * @brief Saves a space to the given storage location + * @param Space The space handle + * @param StorageLocation Which location to store the anchor + * @param StoragePersistenceMode Storage persistence, not valid for cloud storage. + * @param OutRequestId The requestId returned by the system + * @return True if the async call started successfully + */ + bool FOculusXRAnchorManager::SaveAnchor(uint64 Space, + EOculusXRSpaceStorageLocation StorageLocation, + EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId) + { + ovrpSpaceStorageLocation OvrpStorageLocation = ovrpSpaceStorageLocation_Local; + switch(StorageLocation) + { + case EOculusXRSpaceStorageLocation::Invalid: + OvrpStorageLocation = ovrpSpaceStorageLocation_Invalid; + break; + case EOculusXRSpaceStorageLocation::Local: + OvrpStorageLocation = ovrpSpaceStorageLocation_Local; + break; + default: + break; + } + + ovrpSpaceStoragePersistenceMode OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Invalid; + switch(StoragePersistenceMode) + { + case EOculusXRSpaceStoragePersistenceMode::Invalid: + OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Invalid; + break; + case EOculusXRSpaceStoragePersistenceMode::Indefinite: + OvrpStoragePersistenceMode = ovrpSpaceStoragePersistenceMode_Indefinite; + break; + default: + break; + } + + ovrpUInt64 OvrpOutRequestId = 0; + const ovrpResult bSuccess = FOculusXRHMDModule::GetPluginWrapper().SaveSpace(&Space, OvrpStorageLocation, OvrpStoragePersistenceMode, &OvrpOutRequestId); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("Saving space with: SpaceID: %llu -- Location: %d -- Persistence: %d -- OutID: %llu"), Space, OvrpStorageLocation, OvrpStoragePersistenceMode, OvrpOutRequestId); + + memcpy(&OutRequestId,&OvrpOutRequestId,sizeof(uint64)); + + if(bSuccess == ovrpFailure) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("FOculusXRHMD::SaveAnchor failed with: SpaceID: %llu -- Location: %d -- Persistence: %d"), Space, OvrpStorageLocation, OvrpStoragePersistenceMode); + return false; + } + + return true; + } + + /** + * @brief Erases a space from the given storage location + * @param Handle The handle of the space to erase + * @param StorageLocation Where the space to erase is stored + * @param OutRequestId The requestId returned by the system + * @return True if the async call started successfully + */ + bool FOculusXRAnchorManager::EraseAnchor(uint64 AnchorHandle, + EOculusXRSpaceStorageLocation StorageLocation,uint64 &OutRequestId) + { + ovrpSpaceStorageLocation ovrpStorageLocation = ovrpSpaceStorageLocation_Local; + switch(StorageLocation) + { + case EOculusXRSpaceStorageLocation::Invalid: + ovrpStorageLocation = ovrpSpaceStorageLocation_Invalid; + break; + case EOculusXRSpaceStorageLocation::Local: + ovrpStorageLocation = ovrpSpaceStorageLocation_Local; + break; + default: ; + } + + ovrpUInt64 OvrpOutRequestId = 0; + const ovrpResult bSuccess = FOculusXRHMDModule::GetPluginWrapper().EraseSpace(&AnchorHandle, ovrpStorageLocation, &OvrpOutRequestId); + memcpy(&OutRequestId,&OvrpOutRequestId,sizeof(uint64)); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("Erasing anchor -- Handle: %llu -- Location: %d -- OutID: %llu"), AnchorHandle, ovrpStorageLocation, OvrpOutRequestId); + + if(bSuccess == ovrpFailure) + { + return false; + } + return true; + } + + /** + * @brief Queries for spaces given a set of query parameters + * @param QueryInfo The query parameters. + * @param OutRequestId An async work tracking ID. + * @return True if the async call started successfully + */ + bool FOculusXRAnchorManager::QuerySpaces(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId) + { + ovrpSpaceQueryInfo ovrQueryInfo = ConvertToOVRPSpaceQueryInfo(QueryInfo); + + ovrpUInt64 OvrpOutRequestId = 0; + const ovrpResult QuerySpacesResult = FOculusXRHMDModule::GetPluginWrapper().QuerySpaces(&ovrQueryInfo, &OvrpOutRequestId); + memcpy(&OutRequestId, &OvrpOutRequestId, sizeof(uint64)); + + UE_LOG(LogOculusXRAnchors, Log, TEXT("Query Spaces\n ovrpSpaceQueryInfo:\n\tQueryType: %d\n\tMaxQuerySpaces: %d\n\tTimeout: %f\n\tLocation: %d\n\tActionType: %d\n\tFilterType: %d\n\n\tRequest ID: %llu"), + ovrQueryInfo.queryType, ovrQueryInfo.maxQuerySpaces, (float)ovrQueryInfo.timeout, ovrQueryInfo.location, ovrQueryInfo.actionType, ovrQueryInfo.filterType, OutRequestId); + + if (QueryInfo.FilterType == EOculusXRSpaceQueryFilterType::FilterByIds) + { + UE_LOG(LogOculusXRAnchors, Log, TEXT("Query contains %d UUIDs"), QueryInfo.IDFilter.Num()); + for (auto& it : QueryInfo.IDFilter) + { + UE_LOG(LogOculusXRAnchors, Log, TEXT("UUID: %s"), *it.ToString()); + } + } + else if (QueryInfo.FilterType == EOculusXRSpaceQueryFilterType::FilterByComponentType) + { + UE_LOG(LogOculusXRAnchors, Log, TEXT("Query contains %d Component Types"), QueryInfo.ComponentFilter.Num()); + for (auto& it : QueryInfo.ComponentFilter) + { + UE_LOG(LogOculusXRAnchors, Log, TEXT("ComponentType: %s"), *UEnum::GetValueAsString(it)); + } + } + + return OVRP_SUCCESS(QuerySpacesResult); + } + + /** + * @brief Gets bounds of a specific space + * @param Handle The handle of the space + * @param OutPos Position of the rectangle (in local frame of reference). Since it's 2D, X member will be 0.f. + * @param OutSize Size of the rectangle (in local frame of reference). Since it's 2D, X member will be 0.f. + * @return True if successfully retrieved space scene plane + */ + bool FOculusXRAnchorManager::GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize) + { + OutPos.X = OutPos.Y = OutPos.Z = 0.f; + OutSize.X = OutSize.Y = OutSize.Z = 0.f; + + ovrpRectf rect; + const ovrpResult bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundingBox2D(&Space, &rect); + + if (bSuccess == ovrpFailure) + { + return false; + } + + // Convert to UE4's coordinates system + OutPos.Y = rect.Pos.x; + OutPos.Z = rect.Pos.y; + OutSize.Y = rect.Size.w; + OutSize.Z = rect.Size.h; + + return true; + } + + /** + * @brief Gets bounds of a specific space + * @param Handle The handle of the space + * @param OutPos Position of the rectangle (in local frame of reference) + * @param OutSize Size of the rectangle (in local frame of reference) + * @return True if successfully retrieved space scene volume + */ + bool FOculusXRAnchorManager::GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize) + { + OutPos.X = OutPos.Y = OutPos.Z = 0.f; + OutSize.X = OutSize.Y = OutSize.Z = 0.f; + + ovrpBoundsf bounds; + const ovrpResult bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetSpaceBoundingBox3D(&Space, &bounds); + + if (bSuccess == ovrpFailure) + { + return false; + } + + // Convert to UE4's coordinates system + OutPos.X = bounds.Pos.z; + OutPos.Y = bounds.Pos.x; + OutPos.Z = bounds.Pos.y; + OutSize.X = bounds.Size.d; + OutSize.Y = bounds.Size.w; + OutSize.Z = bounds.Size.h; + + return true; + } + + /** + * @brief Gets semantic classification associated with a specific space + * @param Handle The handle of the space + * @param OutSemanticClassifications Array of the semantic classification associated with Space + * @return returns true if successfully retrieved the semantic classification + */ + bool FOculusXRAnchorManager::GetSpaceSemanticClassification(uint64 Space, TArray<FString>& OutSemanticClassifications) + { + OutSemanticClassifications.Empty(); + + const int32 maxByteSize = 1024; + char labelsChars[maxByteSize]; + + ovrpSemanticLabels labels; + labels.byteCapacityInput = maxByteSize; + labels.labels = labelsChars; + + const ovrpResult bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetSpaceSemanticLabels(&Space, &labels); + + if (bSuccess == ovrpFailure) + { + return false; + } + + FString labelsStr(labels.byteCountOutput, labels.labels); + labelsStr.ParseIntoArray(OutSemanticClassifications, TEXT(",")); + + return true; + } +} + diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.h b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.h new file mode 100644 index 0000000000000000000000000000000000000000..7f83dd637622d5ed5a3a9973fc0ae2a40a8656e2 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorManager.h @@ -0,0 +1,33 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#pragma once + +#include "CoreMinimal.h" +#include "OculusXRAnchorComponent.h" +#include "OculusXRHMDPrivate.h" + +namespace OculusXRAnchors +{ + struct OCULUSXRANCHORS_API FOculusXRAnchorManager + { + static bool CreateAnchor(const FTransform& InTransform, uint64& OutRequestId, const FTransform& CameraTransform); + static bool DestroySpace(uint64 Space); + static bool SetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool Enable,float Timeout, uint64& OutRequestId); + static bool GetSpaceComponentStatus(uint64 Space, EOculusXRSpaceComponentType SpaceComponentType, bool &OutEnabled, bool &OutChangePending); + static bool SaveAnchor(uint64 Space, EOculusXRSpaceStorageLocation StorageLocation, EOculusXRSpaceStoragePersistenceMode StoragePersistenceMode, uint64& OutRequestId); + static bool EraseAnchor(uint64 AnchorHandle, EOculusXRSpaceStorageLocation StorageLocation, uint64& OutRequestId); + static bool QuerySpaces(const FOculusXRSpaceQueryInfo& QueryInfo, uint64& OutRequestId); + static bool GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize); + static bool GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize); + static bool GetSpaceSemanticClassification(uint64 Space, TArray<FString>& OutSemanticClassification); + + static void OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult); + }; +} + + diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypes.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypes.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1709c2bc211ac72420cc1d44e6256cecd0b0e506 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorTypes.cpp @@ -0,0 +1,93 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRAnchorTypes.h" +#include "OculusXRHMDPrivate.h" + +bool FOculusXRUInt64::operator==(const FOculusXRUInt64& Right) const { return IsEqual(Right); } +bool FOculusXRUInt64::operator!=(const FOculusXRUInt64& Right) const { return !IsEqual(Right); } + +FOculusXRUUID::FOculusXRUUID() +{ + FMemory::Memzero(&UUIDBytes, OCULUSXR_UUID_SIZE); +} + +FOculusXRUUID::FOculusXRUUID(const ovrpXRUuidArray& UuidArray) +{ + FMemory::Memcpy(UUIDBytes, UuidArray); +} + +bool FOculusXRUUID::operator==(const FOculusXRUUID& Right) const +{ + return IsEqual(Right); +} + +bool FOculusXRUUID::operator!=(const FOculusXRUUID& Right) const +{ + return !IsEqual(Right); +} + +bool FOculusXRUUID::IsValidUUID() const +{ + static uint8 InvalidUUID[OCULUSXR_UUID_SIZE] = { 0 }; + + return FMemory::Memcmp(UUIDBytes, InvalidUUID, OCULUSXR_UUID_SIZE) != 0; +} + +bool FOculusXRUUID::IsEqual(const FOculusXRUUID& Other) const +{ + return FMemory::Memcmp(UUIDBytes, Other.UUIDBytes, OCULUSXR_UUID_SIZE) == 0; +} + +uint32 GetTypeHash(const FOculusXRUUID& Other) +{ + return FCrc::MemCrc32(&Other.UUIDBytes, sizeof(Other.UUIDBytes)); +} + +bool FOculusXRUUID::NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess) +{ + uint8 data[16] = { 0 }; + + for (uint8 i = 0; i < OCULUSXR_UUID_SIZE; ++i) + { + data[i] = UUIDBytes[i]; + }; + + for (uint8 i = 0; i < OCULUSXR_UUID_SIZE; ++i) + { + Ar << data[i]; + }; + + for (uint8 i = 0; i < OCULUSXR_UUID_SIZE; ++i) + { + UUIDBytes[i] = data[i]; + }; + + bOutSuccess = true; + + return true; +} + +FArchive& operator<<(FArchive& Ar, FOculusXRUUID& UUID) +{ + bool bOutSuccess = false; + UUID.NetSerialize(Ar, nullptr, bOutSuccess); + + return Ar; +} + +bool FOculusXRUUID::Serialize(FArchive& Ar) +{ + Ar << *this; + return true; +} + +FString FOculusXRUUID::ToString() const +{ + return BytesToHex(UUIDBytes, OCULUSXR_UUID_SIZE); +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchors.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchors.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e4d9005ca8c7b8d81faa1cced48a756b1205a16e --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchors.cpp @@ -0,0 +1,417 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRAnchors.h" +#include "OculusXRAnchorsModule.h" +#include "OculusXRAnchorDelegates.h" +#include "OculusXRHMDModule.h" +#include "OculusXRAnchorManager.h" +#include "OculusXRSpatialAnchorComponent.h" + +namespace OculusXRAnchors +{ + +void FOculusXRAnchors::Initialize() +{ + DelegateHandleAnchorCreate = FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete.AddRaw(this, &FOculusXRAnchors::HandleSpatialAnchorCreateComplete); + DelegateHandleAnchorErase = FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete.AddRaw(this, &FOculusXRAnchors::HandleAnchorEraseComplete); + DelegateHandleSetComponentStatus = FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete.AddRaw(this, &FOculusXRAnchors::HandleSetComponentStatusComplete); + DelegateHandleAnchorSave = FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete.AddRaw(this, &FOculusXRAnchors::HandleAnchorSaveComplete); + DelegateHandleQueryResultsBegin = FOculusXRAnchorEventDelegates::OculusSpaceQueryResults.AddRaw(this, &FOculusXRAnchors::HandleAnchorQueryResultsBegin); + DelegateHandleQueryResultElement = FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.AddRaw(this, &FOculusXRAnchors::HandleAnchorQueryResultElement); + DelegateHandleQueryComplete = FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.AddRaw(this, &FOculusXRAnchors::HandleAnchorQueryComplete); +} + +void FOculusXRAnchors::Teardown() +{ + FOculusXRAnchorEventDelegates::OculusSpatialAnchorCreateComplete.Remove(DelegateHandleAnchorCreate); + FOculusXRAnchorEventDelegates::OculusSpaceEraseComplete.Remove(DelegateHandleAnchorErase); + FOculusXRAnchorEventDelegates::OculusSpaceSetComponentStatusComplete.Remove(DelegateHandleSetComponentStatus); + FOculusXRAnchorEventDelegates::OculusSpaceSaveComplete.Remove(DelegateHandleAnchorSave); + FOculusXRAnchorEventDelegates::OculusSpaceQueryResults.Remove(DelegateHandleQueryResultsBegin); + FOculusXRAnchorEventDelegates::OculusSpaceQueryResult.Remove(DelegateHandleQueryResultElement); + FOculusXRAnchorEventDelegates::OculusSpaceQueryComplete.Remove(DelegateHandleQueryComplete); +} + +FOculusXRAnchors* FOculusXRAnchors::GetInstance() +{ + return FOculusXRAnchorsModule::GetOculusAnchors(); +} + +bool FOculusXRAnchors::CreateSpatialAnchor(const FTransform& InTransform, AActor* TargetActor, const FOculusXRSpatialAnchorCreateDelegate& ResultCallback) +{ + if (!IsValid(TargetActor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid actor provided when attempting to create a spatial anchor.")); + return false; + } + + UWorld* World = TargetActor->GetWorld(); + if (!IsValid(World)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve World Context while creating spatial anchor.")); + return false; + } + + APlayerController* PlayerController = World->GetFirstPlayerController(); + if (!IsValid(PlayerController)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve Player Controller while creating spatial anchor")); + return false; + } + + APlayerCameraManager* PlayerCameraManager = PlayerController->PlayerCameraManager; + FTransform MainCameraTransform = FTransform::Identity; + if (IsValid(PlayerCameraManager)) + { + MainCameraTransform.SetLocation(PlayerCameraManager->GetCameraLocation()); + MainCameraTransform.SetRotation(FQuat(PlayerCameraManager->GetCameraRotation())); + } + + UOculusXRAnchorComponent* Anchor = Cast<UOculusXRAnchorComponent>(TargetActor->GetComponentByClass(UOculusXRAnchorComponent::StaticClass())); + if (IsValid(Anchor) && Anchor->HasValidHandle()) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Actor targeted to create anchor already has an anchor component with a valid handle.")); + return false; + } + + uint64 RequestId = 0; + bool started = FOculusXRAnchorManager::CreateAnchor(InTransform, RequestId, MainCameraTransform); + if (!started) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to create anchor.")); + return false; + } + + CreateAnchorBinding AnchorData; + AnchorData.RequestId = RequestId; + AnchorData.Actor = TargetActor; + AnchorData.Binding = ResultCallback; + + FOculusXRAnchors* SDKInstance = GetInstance(); + SDKInstance->CreateSpatialAnchorBindings.Add(RequestId, AnchorData); + + return true; +} + +bool FOculusXRAnchors::EraseAnchor(UOculusXRAnchorComponent* Anchor, const FOculusXRAnchorEraseDelegate& ResultCallback) +{ + if (!IsValid(Anchor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to erase an anchor.")); + return false; + } + + if (!Anchor->HasValidHandle()) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Cannot erase anchor with invalid handle.")); + return false; + } + + if (!Anchor->IsStoredAtLocation(EOculusXRSpaceStorageLocation::Local)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Only local anchors can be erased.")); + return false; + } + + uint64 RequestId = 0; + + // Erase only supports local anchors + bool started = FOculusXRAnchorManager::EraseAnchor(Anchor->GetHandle(), EOculusXRSpaceStorageLocation::Local, RequestId); + if (!started) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async erase.")); + return false; + } + + EraseAnchorBinding EraseData; + EraseData.RequestId = RequestId; + EraseData.Binding = ResultCallback; + EraseData.Anchor = Anchor; + + FOculusXRAnchors* SDKInstance = GetInstance(); + SDKInstance->EraseAnchorBindings.Add(RequestId, EraseData); + + return true; +} + +bool FOculusXRAnchors::DestroyAnchor(uint64 AnchorHandle) +{ + return FOculusXRAnchorManager::DestroySpace(AnchorHandle); +} + +bool FOculusXRAnchors::SetAnchorComponentStatus(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, const FOculusXRAnchorSetComponentStatusDelegate& ResultCallback) +{ + if (!IsValid(Anchor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to set anchor component status.")); + return false; + } + + if (!Anchor->HasValidHandle()) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor provided to set anchor component status has invalid handle.")); + return false; + } + + uint64 RequestId = 0; + bool started = FOculusXRAnchorManager::SetSpaceComponentStatus(Anchor->GetHandle(), SpaceComponentType, Enable, Timeout, RequestId); + if (!started) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to set anchor component status.")); + return false; + } + + SetComponentStatusBinding SetComponentStatusData; + SetComponentStatusData.RequestId = RequestId; + SetComponentStatusData.Binding = ResultCallback; + SetComponentStatusData.Anchor = Anchor; + + FOculusXRAnchors* SDKInstance = GetInstance(); + SDKInstance->SetComponentStatusBindings.Add(RequestId, SetComponentStatusData); + + return true; +} + +bool FOculusXRAnchors::GetAnchorComponentStatus(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending) +{ + if (!IsValid(Anchor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to get space component status.")); + return false; + } + + if (!Anchor->HasValidHandle()) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor provided to get space component status has invalid handle.")); + return false; + } + + return FOculusXRAnchorManager::GetSpaceComponentStatus(Anchor->GetHandle(), SpaceComponentType, OutEnabled, OutChangePending); +} + +bool FOculusXRAnchors::SaveAnchor(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceStorageLocation StorageLocation, const FOculusXRAnchorSaveDelegate& ResultCallback) +{ + if (!IsValid(Anchor)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Invalid anchor provided when attempting to save anchor.")); + return false; + } + + if (!Anchor->HasValidHandle()) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Anchor provided to save anchor has invalid handle.")); + return false; + } + + uint64 RequestId = 0; + bool started = FOculusXRAnchorManager::SaveAnchor(Anchor->GetHandle(), StorageLocation, EOculusXRSpaceStoragePersistenceMode::Indefinite, RequestId); + if (!started) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to start async call to save anchor.")); + return false; + } + + SaveAnchorBinding SaveAnchorData; + SaveAnchorData.RequestId = RequestId; + SaveAnchorData.Binding = ResultCallback; + SaveAnchorData.Location = StorageLocation; + SaveAnchorData.Anchor = Anchor; + + FOculusXRAnchors* SDKInstance = GetInstance(); + SDKInstance->AnchorSaveBindings.Add(RequestId, SaveAnchorData); + + return true; +} + +bool FOculusXRAnchors::QueryAnchors(const TArray<FOculusXRUUID>& AnchorUUIDs, EOculusXRSpaceStorageLocation Location, int32 MaxAnchors, const FOculusXRAnchorQueryDelegate& ResultCallback) +{ + FOculusXRSpaceQueryInfo QueryInfo; + QueryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByIds; + QueryInfo.IDFilter = AnchorUUIDs; + QueryInfo.Location = Location; + QueryInfo.MaxQuerySpaces = MaxAnchors; + + return QueryAnchorsAdvanced(QueryInfo, ResultCallback); +} + +bool FOculusXRAnchors::QueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo, const FOculusXRAnchorQueryDelegate& ResultCallback) +{ + uint64 RequestId = 0; + bool started = FOculusXRAnchorManager::QuerySpaces(QueryInfo, RequestId); + if (!started) + { + ResultCallback.ExecuteIfBound(false, TArray<FOculusXRSpaceQueryResult>()); + return false; + } + + AnchorQueryBinding QueryResults; + QueryResults.RequestId = RequestId; + QueryResults.Binding = ResultCallback; + QueryResults.Location = QueryInfo.Location; + + FOculusXRAnchors* SDKInstance = GetInstance(); + SDKInstance->AnchorQueryBindings.Add(RequestId, QueryResults); + + return true; +} + +bool FOculusXRAnchors::GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize) +{ + return FOculusXRAnchorManager::GetSpaceScenePlane(Space, OutPos, OutSize); +} + +bool FOculusXRAnchors::GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize) +{ + return FOculusXRAnchorManager::GetSpaceSceneVolume(Space, OutPos, OutSize); +} + +bool FOculusXRAnchors::GetSpaceSemanticClassification(uint64 Space, TArray<FString>& OutSemanticClassifications) +{ + return FOculusXRAnchorManager::GetSpaceSemanticClassification(Space, OutSemanticClassifications); +} + +void FOculusXRAnchors::HandleSpatialAnchorCreateComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUInt64 Space, FOculusXRUUID UUID) +{ + CreateAnchorBinding* AnchorDataPtr = CreateSpatialAnchorBindings.Find(RequestId.GetValue()); + if (AnchorDataPtr == nullptr) + { + UE_LOG(LogOculusXRAnchors, Error, TEXT("Couldn't find anchor data binding for create spatial anchor! Request: %llu"), RequestId.GetValue()); + return; + } + + if (!OVRP_SUCCESS(Result)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to create Spatial Anchor. Request: %llu -- Result: %d"), RequestId.GetValue(), Result); + AnchorDataPtr->Binding.ExecuteIfBound(false, nullptr); + return; + } + + if (!AnchorDataPtr->Actor.IsValid()) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Actor has been invalidated while creating actor. Request: %llu"), RequestId.GetValue()); + + // Clean up the orphaned space + FOculusXRAnchors::DestroyAnchor(Space); + + AnchorDataPtr->Binding.ExecuteIfBound(false, nullptr); + return; + } + + AActor* TargetActor = AnchorDataPtr->Actor.Get(); + + UOculusXRSpatialAnchorComponent* SpatialAnchorComponent = TargetActor->FindComponentByClass<UOculusXRSpatialAnchorComponent>(); + if (SpatialAnchorComponent == nullptr) + { + SpatialAnchorComponent = Cast<UOculusXRSpatialAnchorComponent>(TargetActor->AddComponentByClass(UOculusXRSpatialAnchorComponent::StaticClass(), false, FTransform::Identity, false)); + } + + SpatialAnchorComponent->SetHandle(Space); + SpatialAnchorComponent->SetUUID(UUID); + + uint64 tempOut; + FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Locatable, true, 0.0f, tempOut); + FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Storable, true, 0.0f, tempOut); + + AnchorDataPtr->Binding.ExecuteIfBound(OVRP_SUCCESS(Result), SpatialAnchorComponent); + CreateSpatialAnchorBindings.Remove(RequestId.GetValue()); +} + +void FOculusXRAnchors::HandleAnchorEraseComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation Location) +{ + EraseAnchorBinding* EraseDataPtr = EraseAnchorBindings.Find(RequestId.GetValue()); + if (EraseDataPtr == nullptr) + { + UE_LOG(LogOculusXRAnchors, Error, TEXT("Couldn't find binding for space erase! Request: %llu"), RequestId.GetValue()); + return; + } + + if (!OVRP_SUCCESS(Result)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to erase Spatial Anchor. Request: %llu -- Result: %d"), RequestId.GetValue(), Result); + EraseDataPtr->Binding.ExecuteIfBound(false, UUID); + return; + } + + if (EraseDataPtr->Anchor.IsValid()) + { + // Since you can only erase local anchors, just unset local anchor storage + EraseDataPtr->Anchor->SetStoredLocation(EOculusXRSpaceStorageLocation::Local, false); + } + + EraseDataPtr->Binding.ExecuteIfBound(OVRP_SUCCESS(Result), UUID); + EraseAnchorBindings.Remove(RequestId.GetValue()); +} + +void FOculusXRAnchors::HandleSetComponentStatusComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUInt64 Space, FOculusXRUUID UUID, EOculusXRSpaceComponentType ComponentType, bool Enabled) +{ + SetComponentStatusBinding* SetStatusBinding = SetComponentStatusBindings.Find(RequestId.GetValue()); + if (SetStatusBinding == nullptr) + { + UE_LOG(LogOculusXRAnchors, Verbose, TEXT("Couldn't find binding for set component status! Request: %llu"), RequestId.GetValue()); + return; + } + + SetStatusBinding->Binding.ExecuteIfBound(OVRP_SUCCESS(Result), SetStatusBinding->Anchor.Get(), ComponentType, Enabled); + SetComponentStatusBindings.Remove(RequestId.GetValue()); +} + +void FOculusXRAnchors::HandleAnchorSaveComplete(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, bool Success, int Result, FOculusXRUUID UUID) +{ + SaveAnchorBinding* SaveAnchorData = AnchorSaveBindings.Find(RequestId.GetValue()); + if (SaveAnchorData == nullptr) + { + UE_LOG(LogOculusXRAnchors, Error, TEXT("Couldn't find binding for save anchor! Request: %llu"), RequestId.GetValue()); + return; + } + + if (!OVRP_SUCCESS(Result)) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Failed to save Spatial Anchor. Request: %llu -- Result: %d -- Space: %llu"), RequestId.GetValue(), Result, Space.GetValue()); + SaveAnchorData->Binding.ExecuteIfBound(false, SaveAnchorData->Anchor.Get()); + return; + } + + if (SaveAnchorData->Anchor.IsValid()) + { + SaveAnchorData->Anchor->SetStoredLocation(SaveAnchorData->Location, true); + } + + SaveAnchorData->Binding.ExecuteIfBound(Success, SaveAnchorData->Anchor.Get()); + AnchorSaveBindings.Remove(RequestId.GetValue()); +} + +void FOculusXRAnchors::HandleAnchorQueryResultsBegin(FOculusXRUInt64 RequestId) +{ + // no op +} + +void FOculusXRAnchors::HandleAnchorQueryResultElement(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID UUID) +{ + AnchorQueryBinding* ResultPtr = AnchorQueryBindings.Find(RequestId.GetValue()); + if (ResultPtr) + { + uint64 tempOut; + FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Locatable, true, 0.0f, tempOut); + FOculusXRAnchorManager::SetSpaceComponentStatus(Space, EOculusXRSpaceComponentType::Storable, true, 0.0f, tempOut); + + ResultPtr->Results.Add(FOculusXRSpaceQueryResult(Space, UUID, ResultPtr->Location)); + } +} + +void FOculusXRAnchors::HandleAnchorQueryComplete(FOculusXRUInt64 RequestId, bool Success) +{ + AnchorQueryBinding* ResultPtr = AnchorQueryBindings.Find(RequestId.GetValue()); + if (ResultPtr) + { + ResultPtr->Binding.ExecuteIfBound(Success, ResultPtr->Results); + AnchorQueryBindings.Remove(RequestId.GetValue()); + } +} + +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7308cba7dc84309e17da1e587875d279864bccc0 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.cpp @@ -0,0 +1,60 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRAnchorsModule.h" + +#if OCULUS_ANCHORS_SUPPORTED_PLATFORMS +#include "OculusXRHMDModule.h" +#include "OculusXRHMD.h" +#include "OculusXRAnchors.h" +#include "OculusXRAnchorManager.h" +#include "OculusXRRoomLayoutManager.h" + +DEFINE_LOG_CATEGORY(LogOculusXRAnchors); + +#define LOCTEXT_NAMESPACE "OculusXRAnchors" + + +//------------------------------------------------------------------------------------------------- +// FOculusXRAnchorsModule +//------------------------------------------------------------------------------------------------- +void FOculusXRAnchorsModule::StartupModule() +{ + if (!GEngine) + { + return; + } + + if (!GEngine->XRSystem.IsValid() || GEngine->XRSystem->GetSystemName() != OculusXRHMD::FOculusXRHMD::OculusSystemName) + { + return; + } + + OculusXRHMD::FOculusXRHMD* HMD = OculusXRHMD::FOculusXRHMD::GetOculusXRHMD(); + if (!HMD) + { + UE_LOG(LogOculusXRAnchors, Warning, TEXT("Unable to retrieve OculusXRHMD, cannot add event polling delegates.")); + return; + } + + HMD->AddEventPollingDelegate(OculusXRHMD::FOculusXRHMDEventPollingDelegate::CreateStatic(&OculusXRAnchors::FOculusXRAnchorManager::OnPollEvent)); + HMD->AddEventPollingDelegate(OculusXRHMD::FOculusXRHMDEventPollingDelegate::CreateStatic(&OculusXRAnchors::FOculusXRRoomLayoutManager::OnPollEvent)); + + Anchors.Initialize(); +} + +void FOculusXRAnchorsModule::ShutdownModule() +{ + Anchors.Teardown(); +} + +OculusXRAnchors::FOculusXRAnchors* FOculusXRAnchorsModule::GetOculusAnchors() +{ + FOculusXRAnchorsModule& Module = FModuleManager::LoadModuleChecked<FOculusXRAnchorsModule>(TEXT("OculusXRAnchors")); + return &Module.Anchors; +} + +#endif // OCULUS_ANCHORS_SUPPORTED_PLATFORMS + +IMPLEMENT_MODULE( FOculusXRAnchorsModule, OculusXRAnchors ) + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.h b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.h new file mode 100644 index 0000000000000000000000000000000000000000..fdde0360616eb53a16bb8a1b2f4d8c4ff3ceae94 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsModule.h @@ -0,0 +1,43 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "IOculusXRAnchorsModule.h" +#include "OculusXRAnchors.h" + +#define LOCTEXT_NAMESPACE "OculusAnchors" + + +//------------------------------------------------------------------------------------------------- +// FOculusXRAnchorsModule +//------------------------------------------------------------------------------------------------- + +#if OCULUS_ANCHORS_SUPPORTED_PLATFORMS + +DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRAnchors, Log, All); + +class FOculusXRAnchorsModule : public IOculusXRAnchorsModule +{ +public: + virtual ~FOculusXRAnchorsModule() = default; + + // IModuleInterface interface + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + static OculusXRAnchors::FOculusXRAnchors* GetOculusAnchors(); + +private: + OculusXRAnchors::FOculusXRAnchors Anchors; +}; + +#else // OCULUS_ANCHORS_SUPPORTED_PLATFORMS + +class FOculusXRAnchorsModule : public FDefaultModuleImpl +{ + +}; + +#endif // OCULUS_ANCHORS_SUPPORTED_PLATFORMS + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsPrivate.h b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsPrivate.h new file mode 100644 index 0000000000000000000000000000000000000000..06d669af92a19049f2bf95c8993318be5ef10c44 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRAnchorsPrivate.h @@ -0,0 +1,11 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "OculusXRAnchorsModule.h" \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0fa030bfebc6ae7d8557ca16f3744d877a7a807a --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.cpp @@ -0,0 +1,106 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#include "OculusXRRoomLayoutManager.h" +#include "OculusXRHMD.h" +#include "OculusXRAnchorDelegates.h" + +namespace OculusXRAnchors +{ + void FOculusXRRoomLayoutManager::OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult) + { + ovrpEventDataBuffer& buf = *EventDataBuffer; + + switch (buf.EventType) + { + case ovrpEventType_None: break; + case ovrpEventType_SceneCaptureComplete: + { + ovrpEventSceneCaptureComplete sceneCaptureComplete; + unsigned char* bufData = buf.EventData; + + memcpy(&sceneCaptureComplete.requestId, bufData, sizeof(sceneCaptureComplete.requestId)); + bufData += sizeof(ovrpUInt64); //move forward + memcpy(&sceneCaptureComplete.result, bufData, sizeof(sceneCaptureComplete.result)); + + + FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.Broadcast(FOculusXRUInt64(sceneCaptureComplete.requestId), sceneCaptureComplete.result >= 0); + break; + } + + default: + { + EventPollResult = false; + break; + } + } + + EventPollResult = true; + } + + /** + * @brief Requests the launch of Capture Flow + * @param OutRequestID The requestId returned by the system + * @return returns true if sucessfull + */ + bool FOculusXRRoomLayoutManager::RequestSceneCapture(uint64& OutRequestID) + { + OutRequestID = 0; + + ovrpSceneCaptureRequest sceneCaptureRequest; + sceneCaptureRequest.request = nullptr; + sceneCaptureRequest.requestByteCount = 0; + + const ovrpResult bSuccess = FOculusXRHMDModule::GetPluginWrapper().RequestSceneCapture(&sceneCaptureRequest, &OutRequestID); + if (bSuccess == ovrpFailure) + { + return false; + } + + return true; + } + + /** + * @brief Gets the room layout for a specific space + * @param Space The space to get the room layout for + * @param MaxWallsCapacity Maximum number of walls to query + * @param OutCeilingUuid The ceiling entity's uuid + * @param OutFloorUuid The floor entity's uuid + * @param OutWallsUuid Array of uuids belonging to the walls in the room layout + * @return returns true if sucessfull + */ + bool FOculusXRRoomLayoutManager::GetSpaceRoomLayout(const uint64 Space, const uint32 MaxWallsCapacity, + FOculusXRUUID &OutCeilingUuid, FOculusXRUUID &OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid) + { + TArray<ovrpUuid> uuids; + uuids.InsertZeroed(0, MaxWallsCapacity); + + ovrpRoomLayout roomLayout; + roomLayout.wallUuidCapacityInput = MaxWallsCapacity; + roomLayout.wallUuids = uuids.GetData(); + + const ovrpResult bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetSpaceRoomLayout(&Space, &roomLayout); + if (bSuccess == ovrpFailure) + { + return false; + } + + OutCeilingUuid = FOculusXRUUID(roomLayout.ceilingUuid.data); + OutFloorUuid = FOculusXRUUID(roomLayout.floorUuid.data); + + OutWallsUuid.Empty(); + OutWallsUuid.InsertZeroed(0, roomLayout.wallUuidCountOutput); + + for (int32 i = 0; i < roomLayout.wallUuidCountOutput; ++i) + { + OutWallsUuid[i] = FOculusXRUUID(roomLayout.wallUuids[i].data); + } + + return true; + } +} + diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.h b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.h new file mode 100644 index 0000000000000000000000000000000000000000..15389f4549edc741094d75f7711262e45ef3fb40 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManager.h @@ -0,0 +1,26 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#pragma once + +#include "CoreMinimal.h" +#include "OculusXRAnchorComponent.h" +#include "OculusXRHMDPrivate.h" + +namespace OculusXRAnchors +{ + struct FOculusXRRoomLayoutManager + { + static bool RequestSceneCapture(uint64& OutRequestID); + static bool GetSpaceRoomLayout(const uint64 Space, const uint32 MaxWallsCapacity, + FOculusXRUUID &OutCeilingUuid, FOculusXRUUID &OutFloorUuid, TArray<FOculusXRUUID>& OutWallsUuid); + + static void OnPollEvent(ovrpEventDataBuffer* EventDataBuffer, bool& EventPollResult); + }; +} + + diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManagerComponent.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManagerComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..817a3222a3eecb1ee9490894e88a837e21e7866a --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRRoomLayoutManagerComponent.cpp @@ -0,0 +1,85 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#include "OculusXRRoomLayoutManagerComponent.h" +#include "OculusXRHMD.h" +#include "OculusXRRoomLayoutManager.h" +#include "OculusXRAnchorDelegates.h" + +UOculusXRRoomLayoutManagerComponent::UOculusXRRoomLayoutManagerComponent(const FObjectInitializer& ObjectInitializer) +{ + bWantsInitializeComponent = true; // so that InitializeComponent() gets called +} + +void UOculusXRRoomLayoutManagerComponent::OnRegister() +{ + Super::OnRegister(); + + FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.AddUObject(this, &UOculusXRRoomLayoutManagerComponent::OculusRoomLayoutSceneCaptureComplete_Handler); +} + +void UOculusXRRoomLayoutManagerComponent::OnUnregister() +{ + Super::OnUnregister(); + + FOculusXRAnchorEventDelegates::OculusSceneCaptureComplete.RemoveAll(this); +} + +void UOculusXRRoomLayoutManagerComponent::InitializeComponent() +{ + Super::InitializeComponent(); +} + +void UOculusXRRoomLayoutManagerComponent::UninitializeComponent() +{ + Super::UninitializeComponent(); +} + +bool UOculusXRRoomLayoutManagerComponent::LaunchCaptureFlow() +{ + UE_LOG(LogOculusXRAnchors, Error, TEXT("Launch capture flow -- UOculusXRRoomLayoutManagerComponent")); + + uint64 OutRequest = 0; + const bool bSuccess = OculusXRAnchors::FOculusXRRoomLayoutManager::RequestSceneCapture(OutRequest); + if (bSuccess) + { + EntityRequestList.Add(OutRequest); + } + + UE_LOG(LogOculusXRAnchors, Error, TEXT("Launch capture flow -- RequestSceneCapture -- %d"), bSuccess); + + return bSuccess; +} + +bool UOculusXRRoomLayoutManagerComponent::GetRoomLayout(FOculusXRUInt64 Space, FOculusXRRoomLayout& RoomLayoutOut, int32 MaxWallsCapacity) +{ + if (MaxWallsCapacity <= 0) + { + return false; + } + + FOculusXRUUID OutCeilingUuid; + FOculusXRUUID OutFloorUuid; + TArray<FOculusXRUUID> OutWallsUuid; + + const bool bSuccess = OculusXRAnchors::FOculusXRRoomLayoutManager::GetSpaceRoomLayout(Space.Value, static_cast<uint32>(MaxWallsCapacity), OutCeilingUuid, OutFloorUuid, OutWallsUuid); + + if (bSuccess) + { + RoomLayoutOut.CeilingUuid = OutCeilingUuid; + + RoomLayoutOut.FloorUuid = OutFloorUuid; + + RoomLayoutOut.WallsUuid.InsertZeroed(0, OutWallsUuid.Num()); + for (int32 i = 0; i < OutWallsUuid.Num(); ++i) + { + RoomLayoutOut.WallsUuid[i]= OutWallsUuid[i]; + } + } + + return bSuccess; +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorComponent.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2c5048c25f6723ed11479738be46acfcffc7e5a4 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorComponent.cpp @@ -0,0 +1,31 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRSpatialAnchorComponent.h" + +DEFINE_LOG_CATEGORY(LogOculusSpatialAnchor); + +UOculusXRSpatialAnchorComponent::UOculusXRSpatialAnchorComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +bool UOculusXRSpatialAnchorComponent::Create(const FTransform& NewAnchorTransform, AActor* OwningActor, const FOculusXRSpatialAnchorCreateDelegate& Callback) +{ + return OculusXRAnchors::FOculusXRAnchors::CreateSpatialAnchor(NewAnchorTransform, OwningActor, Callback); +} + +bool UOculusXRSpatialAnchorComponent::Erase(const FOculusXRAnchorEraseDelegate& Callback) +{ + return OculusXRAnchors::FOculusXRAnchors::EraseAnchor(this, Callback); +} + +bool UOculusXRSpatialAnchorComponent::Save(EOculusXRSpaceStorageLocation Location, const FOculusXRAnchorSaveDelegate& Callback) +{ + return OculusXRAnchors::FOculusXRAnchors::SaveAnchor(this, Location, Callback); +} diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.cpp b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..83be3398ef4ae4680667b85362b32a00d4080049 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.cpp @@ -0,0 +1,18 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRSpatialAnchorManager.h" + + +namespace OculusXRAnchors +{ + bool FOculusXRSpatialAnchorManager::CreateSpatialAnchor(const FTransform& InTransform, uint64& OutRequestId) + { + return CreateAnchor(InTransform, OutRequestId, FTransform::Identity); + } +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.h b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.h new file mode 100644 index 0000000000000000000000000000000000000000..72e47ffbcbb41469a3adbd8a65836c8ff973d23f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Private/OculusXRSpatialAnchorManager.h @@ -0,0 +1,25 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "OculusXRAnchorManager.h" + +namespace OculusXRAnchors +{ + struct FOculusXRSpatialAnchorManager : FOculusXRAnchorManager + { + FOculusXRSpatialAnchorManager() + : FOculusXRAnchorManager() + { + } + + static bool CreateSpatialAnchor(const FTransform &InTransform, uint64 &OutRequestId); + }; +} diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/IOculusXRAnchorsModule.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/IOculusXRAnchorsModule.h new file mode 100644 index 0000000000000000000000000000000000000000..9b2bdabe00137b349df408eb427f8711ddf5198d --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/IOculusXRAnchorsModule.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Modules/ModuleManager.h" + +#define OCULUS_ANCHORS_SUPPORTED_PLATFORMS (PLATFORM_WINDOWS && WINVER > 0x0502) || (PLATFORM_ANDROID_ARM || PLATFORM_ANDROID_ARM64) + + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IOculusXRAnchorsModule : 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 IOculusXRAnchorsModule& Get() + { + return FModuleManager::LoadModuleChecked< IOculusXRAnchorsModule >( "OculusXRAnchors" ); + } + + /** + * 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( "OculusXRAnchors" ); + } +}; + diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorBPFunctionLibrary.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorBPFunctionLibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..53798f92278919b7a4c36b6be81019ef10a04d59 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorBPFunctionLibrary.h @@ -0,0 +1,47 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "Kismet/BlueprintFunctionLibrary.h" +#include "OculusXRAnchorTypes.h" +#include "OculusXRAnchorBPFunctionLibrary.generated.h" + +//Helper +UCLASS() +class OCULUSXRANCHORS_API UOculusXRAnchorBPFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Spawn Oculus Anchor Actor", WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true"), Category = "OculusXR|SpatialAnchor") + static AActor* SpawnActorWithAnchorHandle(UObject* WorldContextObject, FOculusXRUInt64 Handle, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation AnchorLocation, UClass* ActorClass, AActor* Owner, APawn* Instigator, ESpawnActorCollisionHandlingMethod CollisionHandlingMethod); + + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Spawn Oculus Anchor Actor From Query", WorldContext = "WorldContextObject", UnsafeDuringActorConstruction = "true"), Category = "OculusXR|SpatialAnchor") + static AActor* SpawnActorWithAnchorQueryResults(UObject* WorldContextObject, const FOculusXRSpaceQueryResult& QueryResult, UClass* ActorClass, AActor* Owner, APawn* Instigator, ESpawnActorCollisionHandlingMethod CollisionHandlingMethod); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor") + static bool GetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool& bIsEnabled); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor") + static bool GetAnchorTransformByHandle(const FOculusXRUInt64& Handle, FTransform& OutTransform); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "FOculusXRUInt64 To String", CompactNodeTitle = "->", BlueprintAutocast), Category = "OculusXR|SpatialAnchor") + static FString AnchorHandleToString(const FOculusXRUInt64 Value); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "FOculusXRUUID To String", CompactNodeTitle = "->", BlueprintAutocast), Category = "OculusXR|SpatialAnchor") + static FString AnchorUUIDToString(const FOculusXRUUID& Value); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|SpatialAnchor") + static FOculusXRUUID StringToAnchorUUID(const FString& Value); + + UFUNCTION(BlueprintPure, meta = (DisplayName = "FOculusXRUInt64 equal", CompactNodeTitle = "==", Keywords = "equal", BlueprintAutocast), Category = "OculusXR|SpatialAnchor") + static bool IsEqual_FOculusXRUInt64(const FOculusXRUInt64 Left, const FOculusXRUInt64 Right) { return Left == Right; }; + + UFUNCTION(BlueprintPure, meta = (DisplayName = "FOculusXRUUID equal", CompactNodeTitle = "==", Keywords = "equal", BlueprintAutocast), Category = "OculusXR|SpatialAnchor") + static bool IsEqual_FOculusXRUUID(const FOculusXRUUID& Left, const FOculusXRUUID& Right) { return Left.IsEqual(Right); }; +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponent.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..9f0c1d3a1090f9d6d5ffaac42964b663a2b5eb0f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorComponent.h @@ -0,0 +1,62 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "OculusXRAnchorTypes.h" +#include "Components/ActorComponent.h" +#include "OculusXRAnchorComponent.generated.h" + +UCLASS(meta = (DisplayName = "Oculus Anchor Component")) +class OCULUSXRANCHORS_API UOculusXRAnchorComponent : public UActorComponent +{ + GENERATED_BODY() +public: + UOculusXRAnchorComponent(const FObjectInitializer& ObjectInitializer); + + virtual void BeginPlay() override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + + UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target)) + FOculusXRUInt64 GetHandle() const; + + UFUNCTION(BlueprintCallable, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target)) + void SetHandle(FOculusXRUInt64 Handle); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target)) + bool HasValidHandle() const; + + UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target)) + FOculusXRUUID GetUUID() const; + + void SetUUID(FOculusXRUUID NewUUID); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target)) + bool IsStoredAtLocation(EOculusXRSpaceStorageLocation Location) const; + + // Not exposed to BP because this is managed in code + void SetStoredLocation(EOculusXRSpaceStorageLocation Location, bool Stored); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Anchor", meta = (DefaultToSelf = Target)) + bool IsSaved() const; + +protected: + bool bUpdateHeadSpaceTransform; + +private: + FOculusXRUInt64 AnchorHandle; + FOculusXRUUID AnchorUUID; + int32 StorageLocations; + + UPROPERTY() + class APlayerCameraManager* PlayerCameraManager; + + void UpdateAnchorTransform() const; + bool ToWorldSpacePose(FTransform CameraTransform, FTransform& OutTrackingSpaceTransform) const; +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorDelegates.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorDelegates.h new file mode 100644 index 0000000000000000000000000000000000000000..e81f78b5ffa3d445fab13f36ea0526c76cdd3f4a --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorDelegates.h @@ -0,0 +1,106 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreTypes.h" +#include "OculusXRAnchorTypes.h" +#include "Delegates/Delegate.h" + +class FOculusXRAnchorEventDelegates +{ +public: + /* ovrpEventType_SpatialAnchorCreateComplete + * + * SpatialAnchorCreateComplete + * Prefix: + * FOculusXRSpatialAnchorCreateComplete + * Suffix: + * FOculusXRSpatialAnchorCreateCompleteDelegate + */ + DECLARE_MULTICAST_DELEGATE_FourParams(FOculusXRSpatialAnchorCreateCompleteDelegate, FOculusXRUInt64 /*requestId*/, int /*result*/, FOculusXRUInt64 /*space*/, FOculusXRUUID /*uuid*/); + static OCULUSXRANCHORS_API FOculusXRSpatialAnchorCreateCompleteDelegate OculusSpatialAnchorCreateComplete; + + /* ovrpEventType_SpaceSetComponentStatusComplete + * + * SpaceSetComponentStatusComplete + * Prefix: + * FOculusXRSpaceSetComponentStatusComplete + * Suffix: + * FOculusXRSpaceSetComponentStatusCompleteDelegate + */ + DECLARE_MULTICAST_DELEGATE_SixParams(FOculusXRSpaceSetComponentStatusCompleteDelegate, FOculusXRUInt64 /*requestId*/, int /*result*/, FOculusXRUInt64 /*space*/, FOculusXRUUID /*uuid*/, EOculusXRSpaceComponentType /*componenttype */, bool /*enabled*/); + static OCULUSXRANCHORS_API FOculusXRSpaceSetComponentStatusCompleteDelegate OculusSpaceSetComponentStatusComplete; + + /* ovrpEventType_SpaceQueryResults + * + * SpaceQueryResults + * Prefix: + * FOculusXRSpaceQueryResults + * Suffix: + * FOculusXRSpaceQueryResultsDelegate + */ + DECLARE_MULTICAST_DELEGATE_OneParam(FOculusXRSpaceQueryResultsDelegate, FOculusXRUInt64 /*requestId*/); + static OCULUSXRANCHORS_API FOculusXRSpaceQueryResultsDelegate OculusSpaceQueryResults; + + /* SpaceQueryResult (no ovrp event type) + * + * SpaceQueryResult + * Prefix: + * FOculusXRSpaceQueryResult + * Suffix: + * FOculusXRSpaceQueryResultDelegate + */ + DECLARE_MULTICAST_DELEGATE_ThreeParams(FOculusXRSpaceQueryResultDelegate, FOculusXRUInt64 /*requestId*/, FOculusXRUInt64 /* space*/, FOculusXRUUID /*uuid*/); + static OCULUSXRANCHORS_API FOculusXRSpaceQueryResultDelegate OculusSpaceQueryResult; + + /* ovrpEventType_SpaceQueryComplete + * + * SpaceQueryComplete + * Prefix: + * FOculusXRSpaceQueryComplete + * Suffix: + * FOculusXRSpaceQueryCompleteDelegate + */ + DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRSpaceQueryCompleteDelegate, FOculusXRUInt64 /*requestId*/, bool /* complete*/); + static OCULUSXRANCHORS_API FOculusXRSpaceQueryCompleteDelegate OculusSpaceQueryComplete; + + /* ovrpEventType_SpaceSaveComplete + * + * SpaceSaveComplete + * Prefix: + * FOculusXRSpaceSaveComplete + * Suffix: + * FOculusXRSpaceSaveCompleteDelegate + */ + DECLARE_MULTICAST_DELEGATE_FiveParams(FOculusXRSpaceSaveCompleteDelegate, FOculusXRUInt64 /*requestId*/, FOculusXRUInt64 /* space*/, bool /* sucess*/, int /*result*/, FOculusXRUUID /*uuid*/); + static OCULUSXRANCHORS_API FOculusXRSpaceSaveCompleteDelegate OculusSpaceSaveComplete; + + /* ovrpEventType_SpaceEraseComplete + * + * SpaceEraseComplete + * Prefix: + * FOculusXRSpaceEraseComplete + * Suffix: + * FOculusXRSpaceEraseCompleteDelegate + */ + DECLARE_MULTICAST_DELEGATE_FourParams(FOculusXRSpaceEraseCompleteDelegate, FOculusXRUInt64 /*requestId*/, int /* result*/, FOculusXRUUID /*uuid*/, EOculusXRSpaceStorageLocation /*location*/); + static OCULUSXRANCHORS_API FOculusXRSpaceEraseCompleteDelegate OculusSpaceEraseComplete; + + /* ovrpEventType_SceneCaptureComplete + * + * SceneCaptureComplete + * Prefix: + * FOculusXRSceneCaptureComplete + * Suffix: + * FOculusXRSceneCaptureCompleteDelegate + */ + DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRSceneCaptureCompleteDelegate, FOculusXRUInt64 /*requestId*/, bool /*success*/); + static OCULUSXRANCHORS_API FOculusXRSceneCaptureCompleteDelegate OculusSceneCaptureComplete; + +}; diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorLatentActions.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorLatentActions.h new file mode 100644 index 0000000000000000000000000000000000000000..4baa1cc12087318fd6d6c00bd107284d529ee358 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorLatentActions.h @@ -0,0 +1,179 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "Kismet/BlueprintAsyncActionBase.h" +#include "OculusXRAnchorTypes.h" +#include "OculusXRAnchorComponent.h" +#include "OculusXRAnchorLatentActions.generated.h" + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_CreateSpatialAnchor_Success, UOculusXRAnchorComponent*, Anchor); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOculusXR_LatentAction_CreateSpatialAnchor_Failure); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXR_LatentAction_EraseAnchor_Success, AActor*, Actor, FOculusXRUUID, UUID); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOculusXR_LatentAction_EraseAnchor_Failure); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_SaveAnchor_Success, UOculusXRAnchorComponent*, Anchor); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOculusXR_LatentAction_SaveAnchor_Failure); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusXR_LatentAction_QueryAnchors_Success, const TArray<FOculusXRSpaceQueryResult>&, QueryResults); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOculusXR_LatentAction_QueryAnchors_Failure); + +DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams(FOculusXR_LatentAction_SetComponentStatus_Success, UOculusXRAnchorComponent*, Anchor, EOculusXRSpaceComponentType, ComponentType, bool, Enabled); +DECLARE_DYNAMIC_MULTICAST_DELEGATE(FOculusXR_LatentAction_SetComponentStatus_Failure); + +// +// Create Anchor +// +UCLASS() +class OCULUSXRANCHORS_API UOculusXRAsyncAction_CreateSpatialAnchor : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() +public: + virtual void Activate() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) + static UOculusXRAsyncAction_CreateSpatialAnchor* OculusXRAsyncCreateSpatialAnchor(AActor* TargetActor, const FTransform& AnchorTransform); + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_CreateSpatialAnchor_Success Success; + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_CreateSpatialAnchor_Failure Failure; + + // Target actor + UPROPERTY(Transient) + AActor* TargetActor; + + FTransform AnchorTransform; + +private: + void HandleCreateComplete(bool CreateSuccess, UOculusXRAnchorComponent* Anchor); +}; + + +// +// Erase Anchor +// +UCLASS() +class OCULUSXRANCHORS_API UOculusXRAsyncAction_EraseAnchor : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() +public: + virtual void Activate() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) + static UOculusXRAsyncAction_EraseAnchor* OculusXRAsyncEraseAnchor(AActor* TargetActor); + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_EraseAnchor_Success Success; + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_EraseAnchor_Failure Failure; + + // Target actor + UPROPERTY(Transient) + AActor* TargetActor; + + FOculusXRUInt64 DeleteRequestId; + +private: + void HandleEraseAnchorComplete(bool EraseSuccess, FOculusXRUUID UUID); +}; + + +// +// Save Anchor +// +UCLASS() +class OCULUSXRANCHORS_API UOculusXRAsyncAction_SaveAnchor : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() +public: + virtual void Activate() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) + static UOculusXRAsyncAction_SaveAnchor* OculusXRAsyncSaveAnchor(AActor* TargetActor, EOculusXRSpaceStorageLocation StorageLocation); + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_SaveAnchor_Success Success; + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_SaveAnchor_Failure Failure; + + // Target actor + UPROPERTY(Transient) + AActor* TargetActor; + + EOculusXRSpaceStorageLocation StorageLocation; + +private: + void HandleSaveAnchorComplete(bool SaveSuccess, UOculusXRAnchorComponent* Anchor); +}; + + +// +// Query Anchors +// +UCLASS() +class OCULUSXRANCHORS_API UOculusXRAsyncAction_QueryAnchors : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() +public: + virtual void Activate() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) + static UOculusXRAsyncAction_QueryAnchors* OculusXRAsyncQueryAnchors(EOculusXRSpaceStorageLocation Location, const TArray<FOculusXRUUID>& UUIDs, int32 MaxAnchors); + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) + static UOculusXRAsyncAction_QueryAnchors* OculusXRAsyncQueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo); + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_QueryAnchors_Success Success; + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_QueryAnchors_Failure Failure; + + FOculusXRSpaceQueryInfo QueryInfo; + TArray<FOculusXRSpaceQueryResult> QueryResults; + +private: + void HandleQueryAnchorsResults(bool QuerySuccess, const TArray<FOculusXRSpaceQueryResult>& Results); +}; + + +// +// Set Component Status +// +UCLASS() +class OCULUSXRANCHORS_API UOculusXRAsyncAction_SetAnchorComponentStatus : public UBlueprintAsyncActionBase +{ + GENERATED_BODY() +public: + virtual void Activate() override; + + UFUNCTION(BlueprintCallable, meta = (BlueprintInternalUseOnly = "true")) + static UOculusXRAsyncAction_SetAnchorComponentStatus* OculusXRAsyncSetAnchorComponentStatus(AActor* TargetActor, EOculusXRSpaceComponentType ComponentType, bool bEnabled); + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_SetComponentStatus_Success Success; + + UPROPERTY(BlueprintAssignable) + FOculusXR_LatentAction_SetComponentStatus_Failure Failure; + + // Target actor + UPROPERTY(Transient) + AActor* TargetActor; + + EOculusXRSpaceComponentType ComponentType; + bool bEnabled; + +private: + void HandleSetComponentStatusComplete(bool SetStatusSuccess, UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool bResultEnabled); +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorTypes.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorTypes.h new file mode 100644 index 0000000000000000000000000000000000000000..f7a7b391ddc9783defbc0e686b9c50130c0c5cf9 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchorTypes.h @@ -0,0 +1,184 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "OculusXRAnchorTypes.generated.h" + +#define OCULUSXR_UUID_SIZE 16 + +typedef uint8 ovrpXRUuidArray[OCULUSXR_UUID_SIZE]; + +USTRUCT(BlueprintType) +struct OCULUSXRANCHORS_API FOculusXRUUID +{ + GENERATED_BODY() + + FOculusXRUUID(); + FOculusXRUUID(const ovrpXRUuidArray& UuidArray); + + bool operator==(const FOculusXRUUID& Other) const; + bool operator!=(const FOculusXRUUID& Other) const; + + bool IsValidUUID() const; + + bool IsEqual(const FOculusXRUUID& Other) const; + friend uint32 GetTypeHash(const FOculusXRUUID& Other); + bool NetSerialize(FArchive& Ar, class UPackageMap* Map, bool& bOutSuccess); + + OCULUSXRANCHORS_API friend FArchive& operator<<(FArchive& Ar, FOculusXRUUID& UUID); + bool Serialize(FArchive& Ar); + + FString ToString() const; + + uint8 UUIDBytes[OCULUSXR_UUID_SIZE]; +}; + +template<> +struct TStructOpsTypeTraits<FOculusXRUUID> : public TStructOpsTypeTraitsBase2<FOculusXRUUID> +{ + enum + { + WithIdenticalViaEquality = true, + WithNetSerializer = true, + WithSerializer = true + }; +}; + +USTRUCT( BlueprintType ) +struct OCULUSXRANCHORS_API FOculusXRUInt64 +{ + GENERATED_BODY() + + FOculusXRUInt64() : FOculusXRUInt64(0) { } + FOculusXRUInt64( const uint64& Value ) { this->Value = Value; } + + operator uint64() const { return Value; } + bool operator==(const FOculusXRUInt64& Right) const; + bool operator!=(const FOculusXRUInt64& Right) const; + + UPROPERTY() + uint64 Value; + + bool IsEqual(const FOculusXRUInt64& Other) const + { + return Other.Value == Value; + } + + friend uint32 GetTypeHash(const FOculusXRUInt64& Other) + { + return FCrc::MemCrc_DEPRECATED(&Other.Value, sizeof(Other.Value)); + } + + uint64 GetValue() const { return Value; }; + + void SetValue(const uint64 Val) { Value = Val; }; +}; + +template<> +struct TStructOpsTypeTraits<FOculusXRUInt64> : public TStructOpsTypeTraitsBase2<FOculusXRUInt64> +{ + enum + { + WithIdenticalViaEquality = true, + }; +}; + +UENUM(BlueprintType) +enum class EOculusXRSpaceQueryFilterType : uint8 +{ + None = 0 UMETA(DisplayName = "No Filter"), + FilterByIds = 1 UMETA(DisplayName = "Filter queries by UUIDs"), + FilterByComponentType = 2 UMETA(DisplayName = "Filter queries by component type") +}; + +// This is used as a bit-mask +UENUM(BlueprintType) +enum class EOculusXRSpaceStorageLocation : uint8 +{ + Invalid = 0 UMETA(DisplayName = "Invalid"), + Local = 1 << 0 UMETA(DisplayName = "Local") +}; + +UENUM(BlueprintType) +enum class EOculusXRSpaceStoragePersistenceMode : uint8 +{ + Invalid = 0 UMETA(Hidden), + Indefinite = 1 UMETA(DisplayName = "Indefinite"), +}; + +UENUM(BlueprintType) +enum class EOculusXRSpaceComponentType : uint8 +{ + Locatable = 0 UMETA(DisplayName = "Locatable"), + Storable = 1 UMETA(DisplayName = "Storable"), + ScenePlane = 3 UMETA(DisplayName = "ScenePlane"), + SceneVolume = 4 UMETA(DisplayName = "SceneVolume"), + SemanticClassification = 5 UMETA(DisplayName = "SemanticClassification"), + RoomLayout = 6 UMETA(DisplayName = "RoomLayout"), + SpaceContainer = 7 UMETA(DisplayName = "SpaceContainer"), + Undefined = 8 UMETA(DisplayName = "Not defined"), +}; + +USTRUCT(BlueprintType) +struct OCULUSXRANCHORS_API FOculusXRSpaceQueryInfo +{ + GENERATED_BODY() +public: + FOculusXRSpaceQueryInfo() : + MaxQuerySpaces(1024), + Timeout(0), + Location(EOculusXRSpaceStorageLocation::Local), + FilterType(EOculusXRSpaceQueryFilterType::None) + {} + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + int MaxQuerySpaces; + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + float Timeout; + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + EOculusXRSpaceStorageLocation Location; + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + EOculusXRSpaceQueryFilterType FilterType; + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + TArray<FOculusXRUUID> IDFilter; + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + TArray<EOculusXRSpaceComponentType> ComponentFilter; +}; + +USTRUCT(BlueprintType) +struct OCULUSXRANCHORS_API FOculusXRSpaceQueryResult +{ + GENERATED_BODY() +public: + FOculusXRSpaceQueryResult() : Space(0), UUID(), Location(EOculusXRSpaceStorageLocation::Invalid) {} + FOculusXRSpaceQueryResult(FOculusXRUInt64 SpaceHandle, FOculusXRUUID ID, EOculusXRSpaceStorageLocation SpaceLocation) : Space(SpaceHandle), UUID(ID), Location(SpaceLocation) {} + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + FOculusXRUInt64 Space; + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + FOculusXRUUID UUID; + + UPROPERTY(BlueprintReadWrite, Category = "OculusXR|SpatialAnchor") + EOculusXRSpaceStorageLocation Location; +}; + +USTRUCT(BlueprintType) +struct OCULUSXRANCHORS_API FOculusXRSpaceQueryFilterValues +{ + GENERATED_BODY() +public: + TArray<FOculusXRUUID> Uuids; // used if filtering by UUIDs + TArray<EOculusXRSpaceComponentType> ComponentTypes; // used if filtering by component types +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchors.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchors.h new file mode 100644 index 0000000000000000000000000000000000000000..2261e0e50c2f3c5e753a790fe7fc00503c329829 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRAnchors.h @@ -0,0 +1,112 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#pragma once + +#include "CoreMinimal.h" +#include "OculusXRAnchorComponent.h" +#include "OculusXRAnchorTypes.h" + +DECLARE_DELEGATE_TwoParams(FOculusXRSpatialAnchorCreateDelegate, bool /*Success*/, UOculusXRAnchorComponent* /*Anchor*/); +DECLARE_DELEGATE_TwoParams(FOculusXRAnchorEraseDelegate, bool /*Success*/, FOculusXRUUID /*AnchorUUID*/); +DECLARE_DELEGATE_FourParams(FOculusXRAnchorSetComponentStatusDelegate, bool /*Success*/, UOculusXRAnchorComponent* /*Anchor*/, EOculusXRSpaceComponentType /*ComponentType*/, bool /*Enabled*/); +DECLARE_DELEGATE_TwoParams(FOculusXRAnchorSaveDelegate, bool /*Success*/, UOculusXRAnchorComponent* /*Anchor*/); +DECLARE_DELEGATE_TwoParams(FOculusXRAnchorQueryDelegate, bool /*Success*/, const TArray<FOculusXRSpaceQueryResult>& /*Results*/); + +namespace OculusXRAnchors +{ + +struct OCULUSXRANCHORS_API FOculusXRAnchors +{ + void Initialize(); + void Teardown(); + + static FOculusXRAnchors* GetInstance(); + + static bool CreateSpatialAnchor(const FTransform& InTransform, AActor* TargetActor, const FOculusXRSpatialAnchorCreateDelegate& ResultCallback); + static bool EraseAnchor(UOculusXRAnchorComponent* Anchor, const FOculusXRAnchorEraseDelegate& ResultCallback); + static bool DestroyAnchor(uint64 AnchorHandle); + + static bool SetAnchorComponentStatus(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool Enable, float Timeout, const FOculusXRAnchorSetComponentStatusDelegate& ResultCallback); + static bool GetAnchorComponentStatus(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceComponentType SpaceComponentType, bool& OutEnabled, bool& OutChangePending); + + static bool SaveAnchor(UOculusXRAnchorComponent* Anchor, EOculusXRSpaceStorageLocation StorageLocation, const FOculusXRAnchorSaveDelegate& ResultCallback); + + static bool QueryAnchors(const TArray<FOculusXRUUID>& AnchorUUIDs, EOculusXRSpaceStorageLocation Location, int32 MaxAnchors, const FOculusXRAnchorQueryDelegate& ResultCallback); + static bool QueryAnchorsAdvanced(const FOculusXRSpaceQueryInfo& QueryInfo, const FOculusXRAnchorQueryDelegate& ResultCallback); + + static bool GetSpaceScenePlane(uint64 Space, FVector& OutPos, FVector& OutSize); + static bool GetSpaceSceneVolume(uint64 Space, FVector& OutPos, FVector& OutSize); + static bool GetSpaceSemanticClassification(uint64 Space, TArray<FString>& OutSemanticClassifications); + +private: + void HandleSpatialAnchorCreateComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUInt64 Space, FOculusXRUUID UUID); + void HandleAnchorEraseComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUUID UUID, EOculusXRSpaceStorageLocation Location); + + void HandleSetComponentStatusComplete(FOculusXRUInt64 RequestId, int Result, FOculusXRUInt64 Space, FOculusXRUUID UUID, EOculusXRSpaceComponentType ComponentType, bool Enabled); + + void HandleAnchorSaveComplete(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, bool Success, int Result, FOculusXRUUID UUID); + + void HandleAnchorQueryResultsBegin(FOculusXRUInt64 RequestId); + void HandleAnchorQueryResultElement(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID UUID); + void HandleAnchorQueryComplete(FOculusXRUInt64 RequestId, bool Success); + + struct EraseAnchorBinding + { + FOculusXRUInt64 RequestId; + FOculusXRAnchorEraseDelegate Binding; + TWeakObjectPtr<UOculusXRAnchorComponent> Anchor; + }; + + struct SetComponentStatusBinding + { + FOculusXRUInt64 RequestId; + FOculusXRAnchorSetComponentStatusDelegate Binding; + TWeakObjectPtr<UOculusXRAnchorComponent> Anchor; + }; + + struct CreateAnchorBinding + { + FOculusXRUInt64 RequestId; + FOculusXRSpatialAnchorCreateDelegate Binding; + TWeakObjectPtr<AActor> Actor; + }; + + struct SaveAnchorBinding + { + FOculusXRUInt64 RequestId; + FOculusXRAnchorSaveDelegate Binding; + EOculusXRSpaceStorageLocation Location; + TWeakObjectPtr<UOculusXRAnchorComponent> Anchor; + }; + + struct AnchorQueryBinding + { + FOculusXRUInt64 RequestId; + FOculusXRAnchorQueryDelegate Binding; + EOculusXRSpaceStorageLocation Location; + TArray<FOculusXRSpaceQueryResult> Results; + }; + + // Delegate bindings + TMap<uint64, CreateAnchorBinding> CreateSpatialAnchorBindings; + TMap<uint64, EraseAnchorBinding> EraseAnchorBindings; + TMap<uint64, SetComponentStatusBinding> SetComponentStatusBindings; + TMap<uint64, SaveAnchorBinding> AnchorSaveBindings; + TMap<uint64, AnchorQueryBinding> AnchorQueryBindings; + + // Delegate handles + FDelegateHandle DelegateHandleAnchorCreate; + FDelegateHandle DelegateHandleAnchorErase; + FDelegateHandle DelegateHandleSetComponentStatus; + FDelegateHandle DelegateHandleAnchorSave; + FDelegateHandle DelegateHandleQueryResultsBegin; + FDelegateHandle DelegateHandleQueryResultElement; + FDelegateHandle DelegateHandleQueryComplete; +}; + +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRRoomLayoutManagerComponent.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRRoomLayoutManagerComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..0ffe056b1ed40e35bec67d13a78d0dc39077597f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRRoomLayoutManagerComponent.h @@ -0,0 +1,73 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "OculusXRSpatialAnchorComponent.h" +#include "OculusXRRoomLayoutManagerComponent.generated.h" + +// Represents a room layout within a specific space +USTRUCT(BlueprintType) +struct OCULUSXRANCHORS_API FOculusXRRoomLayout +{ + GENERATED_USTRUCT_BODY() + + FOculusXRUUID FloorUuid; + FOculusXRUUID CeilingUuid; + TArray<FOculusXRUUID> WallsUuid; +}; + +UCLASS(meta = (DisplayName = "OculusXR Room Layout Manager Component", BlueprintSpawnableComponent)) +class OCULUSXRANCHORS_API UOculusXRRoomLayoutManagerComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + UOculusXRRoomLayoutManagerComponent(const FObjectInitializer& ObjectInitializer); + + virtual void InitializeComponent() override; + virtual void UninitializeComponent() override; + + virtual void OnRegister() override; + virtual void OnUnregister() override; + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusXRRoomLayoutSceneCaptureCompleteDelegate, + FOculusXRUInt64, requestId, + bool, result); + + DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusXRRoomLayoutSceneCompleteNativeDelegate, FOculusXRUInt64 /*requestId*/, bool /*success*/); + FOculusXRRoomLayoutSceneCompleteNativeDelegate OculusXRRoomLayoutSceneCaptureCompleteNative; + + UPROPERTY(BlueprintAssignable, Category= "OculusXR|Room Layout Manager") + FOculusXRRoomLayoutSceneCaptureCompleteDelegate OculusXRRoomLayoutSceneCaptureComplete; + + // Requests to launch Capture Flow + UFUNCTION(BlueprintCallable, Category = "OculusXR|Room Layout Manager") + bool LaunchCaptureFlow(); + + // Gets room layout for a specific space + UFUNCTION(BlueprintCallable, Category = "OculusXR|Room Layout Manager") + bool GetRoomLayout(FOculusXRUInt64 Space, UPARAM(ref) FOculusXRRoomLayout& RoomLayoutOut, int32 MaxWallsCapacity = 64); + +protected: + UPROPERTY(Transient) + TSet<uint64> EntityRequestList; + +private: + UFUNCTION() + void OculusRoomLayoutSceneCaptureComplete_Handler(FOculusXRUInt64 RequestId, bool bSuccess) + { + if (EntityRequestList.Find(RequestId.Value) != nullptr) + { + OculusXRRoomLayoutSceneCaptureComplete.Broadcast(RequestId, bSuccess); + OculusXRRoomLayoutSceneCaptureCompleteNative.Broadcast(RequestId, bSuccess); + EntityRequestList.Remove(RequestId.Value); + } + } +}; diff --git a/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRSpatialAnchorComponent.h b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRSpatialAnchorComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..1dd1461cbd9a02e4534df35836773e4ffc0f27c1 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRAnchors/Public/OculusXRSpatialAnchorComponent.h @@ -0,0 +1,31 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "OculusXRAnchorComponent.h" +#include "OculusXRAnchors.h" +#include "OculusXRSpatialAnchorComponent.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogOculusSpatialAnchor, Log, All); + +UCLASS(meta = (DisplayName = "Oculus Spatial Anchor Component", BlueprintSpawnableComponent)) +class OCULUSXRANCHORS_API UOculusXRSpatialAnchorComponent : public UOculusXRAnchorComponent +{ + GENERATED_BODY() +public: + UOculusXRSpatialAnchorComponent(const FObjectInitializer& ObjectInitializer); + + static bool Create(const FTransform& NewAnchorTransform, AActor* OwningActor, const FOculusXRSpatialAnchorCreateDelegate& Callback); + + bool Erase(const FOculusXRAnchorEraseDelegate& Callback); + bool Save(EOculusXRSpaceStorageLocation Location, const FOculusXRAnchorSaveDelegate& Callback); + +private: +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXREditor/OculusXREditor.Build.cs b/Plugins/OculusXR/Source/OculusXREditor/OculusXREditor.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..3f76295c7771bee2fe67b4bb10de6be12344d7bd --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/OculusXREditor.Build.cs @@ -0,0 +1,46 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class OculusXREditor : ModuleRules +{ + public OculusXREditor(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] { + "Projects", + "InputCore", + "UnrealEd", + "LevelEditor", + "CoreUObject", + "Engine", + "EngineSettings", + "AndroidRuntimeSettings", + "Slate", + "SlateCore", + "EditorStyle", + "Core", + "OculusXRHMD", + "OVRPluginXR", + "HTTP", + "DesktopPlatform", + "LauncherServices", + "GameProjectGeneration", + "SharedSettingsWidgets", + } + ); + + PrivateIncludePaths.AddRange( + new string[] { + // Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source + "OculusXREditor/Private", + "OculusXRHMD/Private", + }); + + PrivateIncludePathModuleNames.AddRange( + new string[] { + "Settings", + } + ); + } +} diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.cpp b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b057efa2b8c7190ab351c5df47f3476512bb2354 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.cpp @@ -0,0 +1,313 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRBuildAnalytics.h" +#include "GameProjectGenerationModule.h" +#include "OculusXRHMDModule.h" +#include "Runtime/Core/Public/HAL/FileManager.h" + +FOculusBuildAnalytics* FOculusBuildAnalytics::instance = 0; + +FOculusBuildAnalytics* FOculusBuildAnalytics::GetInstance() +{ + if (IOculusXRHMDModule::IsAvailable()) + { + if (instance == NULL) + { + instance = new FOculusBuildAnalytics(); + } + } + + return instance; +} + +bool FOculusBuildAnalytics::IsOculusXRHMDAvailable() +{ + return IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::Get().PreInit(); +} + +void FOculusBuildAnalytics::Shutdown() +{ +} + +FOculusBuildAnalytics::FOculusBuildAnalytics() +{ + bool TelemetryEnabled = false; + if (!GConfig->GetBool(TEXT("/Script/OculusXREditor.OculusXREditorSettings"), TEXT("bEnableOculusBuildTelemetry"), TelemetryEnabled, GEditorIni)) + { + GConfig->SetBool(TEXT("/Script/OculusXREditor.OculusXREditorSettings"), TEXT("bEnableOculusBuildTelemetry"), TelemetryEnabled, GEditorIni); + GConfig->Flush(0); + } + + if (TelemetryEnabled) + { + RegisterLauncherCallback(); + } +} + +void FOculusBuildAnalytics::OnTelemetryToggled(bool Enabled) +{ + if (Enabled) + { + RegisterLauncherCallback(); + } + else + { + if (LauncherCallbackHandle.IsValid()) + { + ILauncherServicesModule& ProjectLauncherServicesModule = FModuleManager::LoadModuleChecked<ILauncherServicesModule>("LauncherServices"); + ProjectLauncherServicesModule.OnCreateLauncherDelegate.Remove(LauncherCallbackHandle); + } + } +} + +void FOculusBuildAnalytics::RegisterLauncherCallback() +{ + ILauncherServicesModule& ProjectLauncherServicesModule = FModuleManager::LoadModuleChecked<ILauncherServicesModule>("LauncherServices"); + LauncherCallbackHandle = ProjectLauncherServicesModule.OnCreateLauncherDelegate.AddRaw(this, &FOculusBuildAnalytics::OnLauncherCreated); +} + +void FOculusBuildAnalytics::OnLauncherCreated(ILauncherRef Launcher) +{ + // Add callback for when launcher worker is started + Launcher->FLauncherWorkerStartedDelegate.AddRaw(this, &FOculusBuildAnalytics::OnLauncherWorkerStarted); +} + +void FOculusBuildAnalytics::OnLauncherWorkerStarted(ILauncherWorkerPtr LauncherWorker, ILauncherProfileRef Profile) +{ + TArray<FString> Platforms = Profile.Get().GetCookedPlatforms(); + if (Platforms.Num() == 1) + { + if (Platforms[0].Equals("Android_ASTC") || Platforms[0].Contains("Windows")) + { + CurrentBuildStage = UNDEFINED_STAGE; + AndroidPackageTime = 0; + UATLaunched = false; + BuildCompleted = false; + CurrentBuildPlatform = Platforms[0]; + TotalBuildTime = 0; + BuildStepCount = 0; + + FOculusXRHMDModule::GetPluginWrapper().SetDeveloperMode(true); + + OutputDirectory = Profile.Get().GetPackageDirectory(); + + // Assign callbacks for stages + LauncherWorker.Get()->OnStageCompleted().AddRaw(this, &FOculusBuildAnalytics::OnStageCompleted); + LauncherWorker.Get()->OnOutputReceived().AddRaw(this, &FOculusBuildAnalytics::OnBuildOutputRecieved); + LauncherWorker.Get()->OnStageStarted().AddRaw(this, &FOculusBuildAnalytics::OnStageStarted); + LauncherWorker.Get()->OnCompleted().AddRaw(this, &FOculusBuildAnalytics::OnCompleted); + + // Get information on what oculus platform we are building for and also the OS platform + FString OculusPlatform; + if (CurrentBuildPlatform.Equals("Android_ASTC")) + { + UEnum* OculusMobileDevices = StaticEnum<EOculusMobileDevice::Type>(); + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + TArray<TEnumAsByte<EOculusMobileDevice::Type>> TargetOculusDevices = Settings->PackageForOculusMobile; + TArray<FString> Devices; + + if (TargetOculusDevices.Contains(EOculusMobileDevice::Quest)) + { + Devices.Add("quest"); + } + if (TargetOculusDevices.Contains(EOculusMobileDevice::Quest2)) + { + Devices.Add("quest2"); + } + OculusPlatform = FString::Join(Devices, TEXT("_")); + } + else if (CurrentBuildPlatform.Contains("Windows")) + { + CurrentBuildPlatform = "Windows"; + OculusPlatform = "rift"; + } + + // Count user asset files + UserAssetCount = 0; + TArray<FString> FileNames; + IFileManager::Get().FindFilesRecursive(FileNames, *FPaths::ProjectContentDir(), TEXT("*.*"), true, false, false); + UserAssetCount = FileNames.Num(); + + // Count user script files + FGameProjectGenerationModule& GameProjectModule = FModuleManager::LoadModuleChecked<FGameProjectGenerationModule>(TEXT("GameProjectGeneration")); + SourceFileCount = 0; + SourceFileDirectorySize = 0; + GameProjectModule.Get().GetProjectSourceDirectoryInfo(SourceFileCount, SourceFileDirectorySize); + + // Generate build GUID + FGuid guid = FGuid::NewGuid(); + FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("build_guid", TCHAR_TO_ANSI(*guid.ToString())); + + // Send build start event with corresponding metadata + FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("asset_count", TCHAR_TO_ANSI(*FString::FromInt(UserAssetCount))); + FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("script_count", TCHAR_TO_ANSI(*FString::FromInt(SourceFileCount))); + + FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("target_platform", TCHAR_TO_ANSI(*CurrentBuildPlatform)); + FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("target_oculus_platform", TCHAR_TO_ANSI(*OculusPlatform)); + + TArray<ILauncherTaskPtr> TaskList; + LauncherWorker->GetTasks(TaskList); + BuildStepCount = TaskList.Num(); + } + } +} + +void FOculusBuildAnalytics::OnCompleted(bool Succeeded, double TotalTime, int32 ErrorCode) +{ + if (!BuildCompleted && Succeeded) + { + SendBuildCompleteEvent(TotalTime); + } +} + +void FOculusBuildAnalytics::OnStageCompleted(const FString& StageName, double Time) +{ + if (CurrentBuildStage != UNDEFINED_STAGE) + { + FString TaskName; + switch (CurrentBuildStage) + { + case COOK_IN_EDITOR_STAGE: TaskName = "build_step_editor_cook"; break; + case LAUNCH_UAT_STAGE: TaskName = "build_step_launch_uat"; break; + case COMPILE_STAGE: TaskName = "build_step_compile"; break; + case COOK_STAGE: TaskName = "build_step_cook"; break; + case DEPLOY_STAGE: TaskName = "build_step_deploy"; break; + case PACKAGE_STAGE: TaskName = "build_step_package"; break; + case RUN_STAGE: return; + default: TaskName = "build_step_undefined"; break; + } + + if (AndroidPackageTime > 0) + { + Time -= AndroidPackageTime; + } + + TotalBuildTime += Time; + FOculusXRHMDModule::GetPluginWrapper().SendEvent2(TCHAR_TO_ANSI(*TaskName), TCHAR_TO_ANSI(*FString::SanitizeFloat(Time)), "ovrbuild"); + } +} + +void FOculusBuildAnalytics::OnStageStarted(const FString& StageName) +{ + if (StageName.Equals("Cooking in the editor")) + { + CurrentBuildStage = COOK_IN_EDITOR_STAGE; + } + else if (StageName.Equals("Build Task") && CurrentBuildStage == LAUNCH_UAT_STAGE) + { + CurrentBuildStage = COMPILE_STAGE; + } + else if (StageName.Equals("Build Task")) + { + CurrentBuildStage = LAUNCH_UAT_STAGE; + } + else if (StageName.Equals("Cook Task")) + { + CurrentBuildStage = COOK_STAGE; + } + else if (StageName.Equals("Package Task")) + { + CurrentBuildStage = PACKAGE_STAGE; + } + else if (StageName.Equals("Deploy Task")) + { + CurrentBuildStage = DEPLOY_STAGE; + } + else if (StageName.Equals("Run Task")) + { + CurrentBuildStage = RUN_STAGE; + SendBuildCompleteEvent(TotalBuildTime); + BuildCompleted = true; + } + else + { + CurrentBuildStage = UNDEFINED_STAGE; + } +} + +void FOculusBuildAnalytics::OnBuildOutputRecieved(const FString& Message) +{ + if (CurrentBuildPlatform.Equals("Android_ASTC") && (CurrentBuildStage == DEPLOY_STAGE || CurrentBuildStage == PACKAGE_STAGE)) + { + if (Message.Contains("BUILD SUCCESSFUL")) + { + FString Text, Time; + Message.Split("in", &Text, &Time); + + if (!Time.IsEmpty()) + { + FString SMinutes, SSeconds; + if (Time.Contains("m")) + { + Time.Split("m", &SMinutes, &SSeconds); + } + else + { + SSeconds = Time; + } + + int Minutes = FCString::Atoi(*SMinutes); + int Seconds = FCString::Atoi(*SSeconds); + + AndroidPackageTime = Minutes * 60 + Seconds; + + FOculusXRHMDModule::GetPluginWrapper().SendEvent2("build_step_gradle_build", TCHAR_TO_ANSI(*FString::SanitizeFloat(AndroidPackageTime)), "ovrbuild"); + } + } + } +} + +void FOculusBuildAnalytics::SendBuildCompleteEvent(float TotalTime) +{ + if (CurrentBuildPlatform.Equals("Android_ASTC")) + { + int64 APKTotalSize = 0; + TArray<FString> FoundAPKs; + OutputDirectory = FPaths::ProjectDir() + "Binaries/Android"; + OutputDirectory = FPaths::ConvertRelativePathToFull(OutputDirectory); + IFileManager::Get().FindFiles(FoundAPKs, *FPaths::Combine(OutputDirectory, TEXT("*.apk")), true, false); + + FDateTime LatestTime = FDateTime(0); + FString LatestAPK; + for (int i = 0; i < FoundAPKs.Num(); i++) + { + FDateTime APKCreationTime = IFileManager::Get().GetTimeStamp(*FPaths::Combine(OutputDirectory, FoundAPKs[i])); + if (APKCreationTime > LatestTime) + { + LatestTime = APKCreationTime; + LatestAPK = FoundAPKs[i]; + } + } + + TArray<FString> FoundOBBs; + LatestTime = FDateTime(0); + FString LatestOBB; + IFileManager::Get().FindFiles(FoundOBBs, *FPaths::Combine(OutputDirectory, TEXT("*.obb")), true, false); + for (int i = 0; i < FoundOBBs.Num(); i++) + { + FDateTime OBBCreationTime = IFileManager::Get().GetTimeStamp(*FPaths::Combine(OutputDirectory, FoundOBBs[i])); + if (OBBCreationTime > LatestTime) + { + LatestTime = OBBCreationTime; + LatestOBB = FoundOBBs[i]; + } + } + + if (!LatestAPK.IsEmpty()) + { + APKTotalSize += IFileManager::Get().FileSize(*FPaths::Combine(OutputDirectory, LatestAPK)); + } + if (!LatestOBB.IsEmpty()) + { + APKTotalSize += IFileManager::Get().FileSize(*FPaths::Combine(OutputDirectory, LatestOBB)); + } + + if (APKTotalSize > 0) + { + FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("build_output_size", TCHAR_TO_ANSI(*FString::FromInt(APKTotalSize))); + } + } + + FOculusXRHMDModule::GetPluginWrapper().AddCustomMetadata("build_step_count", TCHAR_TO_ANSI(*FString::FromInt(BuildStepCount))); + FOculusXRHMDModule::GetPluginWrapper().SendEvent2("build_complete", TCHAR_TO_ANSI(*FString::SanitizeFloat(TotalTime)), "ovrbuild"); +} diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.h b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.h new file mode 100644 index 0000000000000000000000000000000000000000..16112fd1ed82406c7d76763292dacebbe4ff5bcf --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRBuildAnalytics.h @@ -0,0 +1,61 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "ILauncherServicesModule.h" +#include "ILauncher.h" +#include "Modules/ModuleManager.h" +#include "UObject/Class.h" +#include "AndroidRuntimeSettings.h" +#include "OculusXRPluginWrapper.h" + +enum EBuildStage +{ + UNDEFINED_STAGE, + COOK_IN_EDITOR_STAGE, + COOK_STAGE, + LAUNCH_UAT_STAGE, + COMPILE_STAGE, + PACKAGE_STAGE, + DEPLOY_STAGE, + RUN_STAGE, +}; + +class FOculusBuildAnalytics +{ +public: + static FOculusBuildAnalytics* GetInstance(); + static void Shutdown(); + static bool IsOculusXRHMDAvailable(); + + void RegisterLauncherCallback(); + void OnTelemetryToggled(bool Enabled); + + void OnLauncherCreated(ILauncherRef Launcher); + void OnLauncherWorkerStarted(ILauncherWorkerPtr LauncherWorker, ILauncherProfileRef Profile); + void OnStageCompleted(const FString& StageName, double Time); + void OnStageStarted(const FString& StageName); + void OnBuildOutputRecieved(const FString& Message); + void OnCompleted(bool Succeeded, double TotalTime, int32 ErrorCode); + void SendBuildCompleteEvent(float TotalTime); + +private: + FOculusBuildAnalytics(); + + static FOculusBuildAnalytics* instance; + + FDelegateHandle LauncherCallbackHandle; + + float TotalBuildTime; + float AndroidPackageTime; + bool BuildCompleted; + bool UATLaunched; + int UserAssetCount; + int BuildStepCount; + int32 SourceFileCount; + int64 SourceFileDirectorySize; + + EBuildStage CurrentBuildStage; + FString CurrentBuildPlatform; + FString OutputDirectory; +}; diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorModule.cpp b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..72d490004f97a00eb681acdf03fac600115a8803 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorModule.cpp @@ -0,0 +1,261 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXREditorModule.h" +#include "OculusXRToolStyle.h" +#include "OculusXRToolCommands.h" +#include "OculusXRToolWidget.h" +#include "OculusXRPlatformToolWidget.h" +#include "OculusXRAssetDirectory.h" +#include "OculusXRHMDRuntimeSettings.h" +#include "LevelEditor.h" +#include "Modules/ModuleManager.h" +#include "Widgets/Docking/SDockTab.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Images/SImage.h" +#include "PropertyEditorModule.h" +#include "DetailLayoutBuilder.h" +#include "DetailCategoryBuilder.h" +#include "DetailWidgetRow.h" +#include "Framework/MultiBox/MultiBoxBuilder.h" +#include "ISettingsModule.h" +#include "OculusXREditorSettings.h" + +#define LOCTEXT_NAMESPACE "OculusXREditor" + +const FName FOculusXREditorModule::OculusPerfTabName = FName("OculusXRPerfCheck"); +const FName FOculusXREditorModule::OculusPlatToolTabName = FName("OculusXRPlaformTool"); + +void FOculusXREditorModule::PostLoadCallback() +{ + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor"); +} + +void FOculusXREditorModule::StartupModule() +{ + bModuleValid = true; + RegisterSettings(); + FOculusAssetDirectory::LoadForCook(); + + if (!IsRunningCommandlet()) + { + FOculusToolStyle::Initialize(); + FOculusToolStyle::ReloadTextures(); + + FOculusToolCommands::Register(); + + PluginCommands = MakeShareable(new FUICommandList); + + PluginCommands->MapAction( + FOculusToolCommands::Get().OpenPluginWindow, + FExecuteAction::CreateRaw(this, &FOculusXREditorModule::PluginButtonClicked), + FCanExecuteAction()); + PluginCommands->MapAction( + FOculusToolCommands::Get().ToggleDeploySo, + FExecuteAction::CreateLambda([=]() { + UOculusXRHMDRuntimeSettings *settings = GetMutableDefault<UOculusXRHMDRuntimeSettings>(); + settings->bDeploySoToDevice = !settings->bDeploySoToDevice; + }), + FCanExecuteAction(), + FIsActionChecked::CreateLambda([=]() { + return GetMutableDefault<UOculusXRHMDRuntimeSettings>()->bDeploySoToDevice; + }) + ); + + FLevelEditorModule& LevelEditorModule = FModuleManager::LoadModuleChecked<FLevelEditorModule>("LevelEditor"); + + // Adds an option to launch the tool to Window->Developer Tools. + TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender()); + MenuExtender->AddMenuExtension("Miscellaneous", EExtensionHook::After, PluginCommands, FMenuExtensionDelegate::CreateRaw(this, &FOculusXREditorModule::AddMenuExtension)); + LevelEditorModule.GetMenuExtensibilityManager()->AddExtender(MenuExtender); + + // We add the Oculus menu on the toolbar + TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender); + ToolbarExtender->AddToolBarExtension("Play", EExtensionHook::After, PluginCommands, FToolBarExtensionDelegate::CreateRaw(this, &FOculusXREditorModule::AddToolbarExtension)); + LevelEditorModule.GetToolBarExtensibilityManager()->AddExtender(ToolbarExtender); + + FGlobalTabmanager::Get()->RegisterNomadTabSpawner(OculusPerfTabName, FOnSpawnTab::CreateRaw(this, &FOculusXREditorModule::OnSpawnPluginTab)) + .SetDisplayName(LOCTEXT("FOculusXREditorTabTitle", "Meta XR Performance Check")) + .SetMenuType(ETabSpawnerMenuType::Hidden); + + FGlobalTabmanager::Get()->RegisterNomadTabSpawner(OculusPlatToolTabName, FOnSpawnTab::CreateRaw(this, &FOculusXREditorModule::OnSpawnPlatToolTab)) + .SetDisplayName(LOCTEXT("FOculusPlatfToolTabTitle", "Meta XR Platform Tool")) + .SetMenuType(ETabSpawnerMenuType::Hidden); + } +} + +void FOculusXREditorModule::ShutdownModule() +{ + if (!bModuleValid) + { + return; + } + + if (!IsRunningCommandlet()) + { + FOculusToolStyle::Shutdown(); + FOculusToolCommands::Unregister(); + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(OculusPerfTabName); + FGlobalTabmanager::Get()->UnregisterNomadTabSpawner(OculusPlatToolTabName); + } + + FOculusAssetDirectory::ReleaseAll(); + if (UObjectInitialized()) + { + UnregisterSettings(); + } +} + +TSharedRef<SDockTab> FOculusXREditorModule::OnSpawnPluginTab(const FSpawnTabArgs& SpawnTabArgs) +{ + auto myTab = SNew(SDockTab) + .TabRole(ETabRole::NomadTab) + [ + SNew(SOculusToolWidget) + ]; + + + return myTab; +} + +TSharedRef<SDockTab> FOculusXREditorModule::OnSpawnPlatToolTab(const FSpawnTabArgs& SpawnTabArgs) +{ + auto myTab = SNew(SDockTab) + .TabRole(ETabRole::NomadTab) + [ + SNew(SOculusPlatformToolWidget) + ]; + + return myTab; +} + +void FOculusXREditorModule::RegisterSettings() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings")) + { + SettingsModule->RegisterSettings("Project", "Plugins", "OculusXR", + LOCTEXT("RuntimeSettingsName", "Meta XR"), + LOCTEXT("RuntimeSettingsDescription", "Configure the Meta XR plugin"), + GetMutableDefault<UOculusXRHMDRuntimeSettings>() + ); + + FPropertyEditorModule& PropertyModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>("PropertyEditor"); + PropertyModule.RegisterCustomClassLayout(UOculusXRHMDRuntimeSettings::StaticClass()->GetFName(), FOnGetDetailCustomizationInstance::CreateStatic(&FOculusXRHMDSettingsDetailsCustomization::MakeInstance)); + } +} + +void FOculusXREditorModule::UnregisterSettings() +{ + if (ISettingsModule* SettingsModule = FModuleManager::GetModulePtr<ISettingsModule>("Settings")) + { + SettingsModule->UnregisterSettings("Project", "Plugins", "OculusXR"); + } +} + +FReply FOculusXREditorModule::PluginClickFn(bool text) +{ + PluginButtonClicked(); + return FReply::Handled(); +} + +void FOculusXREditorModule::PluginButtonClicked() +{ + FGlobalTabmanager::Get()->TryInvokeTab(OculusPerfTabName); +} + +void FOculusXREditorModule::AddMenuExtension(FMenuBuilder& Builder) +{ + bool v = false; + GConfig->GetBool(TEXT("/Script/OculusXREditor.OculusXREditorSettings"), TEXT("bAddMenuOption"), v, GEditorIni); + if (v) + { + Builder.AddMenuEntry(FOculusToolCommands::Get().OpenPluginWindow); + } +} + +void FOculusXREditorModule::AddToolbarExtension(FToolBarBuilder& Builder) +{ + Builder.SetLabelVisibility(EVisibility::All); + Builder.AddComboButton( + FUIAction(), + FOnGetContent::CreateRaw(this, &FOculusXREditorModule::CreateToolbarEntryMenu, PluginCommands), + LOCTEXT("OculusToolsToolBarCombo", "Meta XR Tools"), + LOCTEXT("OculusToolsToolBarComboTooltip", "Meta XR tools"), + TAttribute<FSlateIcon>::CreateLambda([]() { + return FSlateIcon(FOculusToolStyle::GetStyleSetName(), "OculusTool.MenuButton"); + }), + false + ); +} + +// Add the entries to the OculusXR Tools toolbar menu button +TSharedRef<SWidget> FOculusXREditorModule::CreateToolbarEntryMenu(TSharedPtr<class FUICommandList> Commands) +{ + FMenuBuilder MenuBuilder(true, Commands); + MenuBuilder.BeginSection("OculusXRBuilds", LOCTEXT("OculusXRBuilds", "Builds")); + MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().ToggleDeploySo); + MenuBuilder.EndSection(); + // If you want to make the tool even easier to launch, and add a toolbar button. + //MenuBuilder.AddMenuEntry(FOculusToolCommands::Get().OpenPluginWindow); + + return MenuBuilder.MakeWidget(); +} + +TSharedRef<IDetailCustomization> FOculusXRHMDSettingsDetailsCustomization::MakeInstance() +{ + return MakeShareable(new FOculusXRHMDSettingsDetailsCustomization); +} + +FReply FOculusXRHMDSettingsDetailsCustomization::PluginClickPerfFn(bool text) +{ + FGlobalTabmanager::Get()->TryInvokeTab(FOculusXREditorModule::OculusPerfTabName); + return FReply::Handled(); +} + +FReply FOculusXRHMDSettingsDetailsCustomization::PluginClickPlatFn(bool text) +{ + FGlobalTabmanager::Get()->TryInvokeTab(FOculusXREditorModule::OculusPlatToolTabName); + return FReply::Handled(); +} + +void FOculusXRHMDSettingsDetailsCustomization::CustomizeDetails(IDetailLayoutBuilder& DetailLayout) +{ + // Labeled "General OculusXR" instead of "General" to enable searchability. The button "Launch Oculus Utilities Window" doesn't show up if you search for "Oculus" + IDetailCategoryBuilder& CategoryBuilder = DetailLayout.EditCategory("General Meta XR", FText::GetEmpty(), ECategoryPriority::Important); + CategoryBuilder.AddCustomRow(LOCTEXT("General", "General")) + .WholeRowContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot().AutoHeight().Padding(2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("LaunchTool", "Launch Meta XR Performance Window")) + .OnClicked(this, &FOculusXRHMDSettingsDetailsCustomization::PluginClickPerfFn, true) + ] + + SHorizontalBox::Slot().FillWidth(8) + ] + + SVerticalBox::Slot().AutoHeight().Padding(2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("LaunchPlatTool", "Launch Meta XR Platform Window")) + .OnClicked(this, &FOculusXRHMDSettingsDetailsCustomization::PluginClickPlatFn, true) + ] + + SHorizontalBox::Slot().FillWidth(8) + ] + ]; +} + +////////////////////////////////////////////////////////////////////////// + +IMPLEMENT_MODULE(FOculusXREditorModule, OculusXREditor); + +////////////////////////////////////////////////////////////////////////// + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorModule.h b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorModule.h new file mode 100644 index 0000000000000000000000000000000000000000..fe1c40cc361439d611f77b7b999738ab74e76479 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorModule.h @@ -0,0 +1,67 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "IOculusXREditorModule.h" +#include "Modules/ModuleInterface.h" +#include "IDetailCustomization.h" +#include "Input/Reply.h" +#include "Layout/Visibility.h" + +class FToolBarBuilder; +class FMenuBuilder; + +#define OCULUS_EDITOR_MODULE_NAME "OculusXREditor" + +enum class ECheckBoxState : uint8; + +class FOculusXREditorModule : public IOculusXREditorModule +{ +public: + FOculusXREditorModule() : bModuleValid(false) {}; + + /** IModuleInterface implementation */ + virtual void StartupModule() override; + virtual void ShutdownModule() override; + virtual void PostLoadCallback() override; + + void RegisterSettings(); + void UnregisterSettings(); + + void PluginButtonClicked(); + FReply PluginClickFn(bool text); + +public: + static const FName OculusPerfTabName; + static const FName OculusPlatToolTabName; + +private: + + void AddToolbarExtension(FToolBarBuilder& Builder); + TSharedRef<SWidget> CreateToolbarEntryMenu(TSharedPtr<class FUICommandList> Commands); + void AddMenuExtension(FMenuBuilder& Builder); + + TSharedRef<class SDockTab> OnSpawnPluginTab(const class FSpawnTabArgs& SpawnTabArgs); + TSharedRef<class SDockTab> OnSpawnPlatToolTab(const class FSpawnTabArgs& SpawnTabArgs); + +private: + TSharedPtr<class FUICommandList> PluginCommands; + bool bModuleValid; +}; + +class IDetailLayoutBuilder; + +class FOculusXRHMDSettingsDetailsCustomization : public IDetailCustomization +{ +public: + /** Makes a new instance of this detail layout class for a specific detail view requesting it */ + static TSharedRef<IDetailCustomization> MakeInstance(); + + // IDetailCustomization interface + virtual void CustomizeDetails(IDetailLayoutBuilder& DetailLayout) override; + // End of IDetailCustomization interface + + FReply PluginClickPerfFn(bool text); + FReply PluginClickPlatFn(bool text); +}; diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorSettings.cpp b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorSettings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..729a8abbb5aed38806cc0f82abd98623298a6000 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXREditorSettings.cpp @@ -0,0 +1,12 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXREditorSettings.h" + + +UOculusXREditorSettings::UOculusXREditorSettings() + : PerfToolTargetPlatform(EOculusXRPlatform::PC) +{ + +} + + diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolSettings.cpp b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolSettings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..416dbfe301eb53ce5a9d02d842b1bfc6c429ab22 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolSettings.cpp @@ -0,0 +1,27 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRPlatformToolSettings.h" + + +UOculusXRPlatformToolSettings::UOculusXRPlatformToolSettings() + : OculusTargetPlatform(EOculusXRPlatformTarget::Rift) +{ + uint8 NumPlatforms = (uint8)EOculusXRPlatformTarget::Length; + OculusApplicationID.Init("", NumPlatforms); + OculusApplicationToken.Init("", NumPlatforms); + OculusReleaseChannel.Init("Alpha", NumPlatforms); + OculusReleaseNote.Init("", NumPlatforms); + OculusLaunchFilePath.Init("", NumPlatforms); + OculusSymbolDirPath.Init("", NumPlatforms); + OculusLanguagePacksPath.Init("", NumPlatforms); + OculusExpansionFilesPath.Init("", NumPlatforms); + OculusAssetConfigs.Init(FOculusXRAssetConfigArray(), NumPlatforms); + UploadDebugSymbols = true; + + for (int i = 0; i < NumPlatforms; i++) + { + OculusAssetConfigs[i].ConfigArray = TArray<FOculusXRAssetConfig>(); + } +} + + diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolWidget.cpp b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b931129d94c4a248022254cadf76fe859441e224 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolWidget.cpp @@ -0,0 +1,1777 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "OculusXRPlatformToolWidget.h" +#include "Widgets/Text/SRichTextBlock.h" +#include "DesktopPlatformModule.h" +#include "Editor.h" +#include "Styling/AppStyle.h" +#include "Misc/FileHelper.h" +#include "Internationalization/Regex.h" +#include "Misc/MessageDialog.h" +#include "Widgets/Layout/SExpandableArea.h" +#include "Widgets/Images/SImage.h" +#include "HAL/FileManagerGeneric.h" +#include "DOM/JsonObject.h" +#include "Serialization/JsonSerializer.h" +#include "OculusXRHMDModule.h" +#include "GenericPlatform/GenericPlatformMisc.h" +#include "Interfaces/IPluginManager.h" +#include "SHyperlinkLaunchURL.h" + +#define LOCTEXT_NAMESPACE "OculusPlatformToolWidget" +#define TEXT_INDENT_OFFSET 20.0f + +const FString UrlPlatformUtil = "https://www.oculus.com/download_app/?id=1076686279105243"; +const FString ProjectPlatformUtilPath = "Oculus/Tools/ovr-platform-util.exe"; + +FText OculusPlatformDialogTitle = LOCTEXT("DownloadOculusPlatformUtility", "Download Oculus Platform Utility"); +FText OculusPlatformDialogMessage = LOCTEXT("DownloadOculusPlatformUtilityMessage", + "Oculus Platform Window would like to download the latest version of the Oculus Platform Utility." + " Oculus Platform Utility is a command-line tool that enables the uploading of builds to your release channels on the Oculus Developer Dashboard." + "\n\nYou can learn more about the Oculus Platform Utility at https://developer.oculus.com/distribute/publish-reference-platform-command-line-utility/" + "\n\nCanceling will prevent the download and the UPLOAD button will be unfunctional. Would you like the tool to download the Oculus Platform Utility to your project?" +); + +static bool bShowUploadDebugSymbols = false; + +FString SOculusPlatformToolWidget::LogText; + +SOculusPlatformToolWidget::SOculusPlatformToolWidget() +{ + LogTextUpdated = false; + ActiveUploadButton = true; + Options2DCollapsed = true; + RequestUploadButtonActive = true; + OptionsRedistPackagesCollapsed = true; + + EnableUploadButtonDel.BindRaw(this, &SOculusPlatformToolWidget::EnableUploadButton); + UpdateLogTextDel.BindRaw(this, &SOculusPlatformToolWidget::UpdateLogText); + SetProcessDel.BindRaw(this, &SOculusPlatformToolWidget::SetPlatformProcess); + + LoadConfigSettings(); + + FOculusXRHMDModule::GetPluginWrapper().SendEvent2("oculus_platform_tool", "show_window", "integration"); +} + +void SOculusPlatformToolWidget::Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) +{ + // Update log text if it changes, otherwise constant updating will yeild the field unselectable. + if (LogTextUpdated) + { + ToolConsoleLog->SetText(FText::FromString(LogText)); + LogTextUpdated = false; + } + + if (RequestUploadButtonActive != ActiveUploadButton) + { + ActiveUploadButton = RequestUploadButtonActive; + BuildButtonToolbar(ButtonToolbar); + } +} + +void SOculusPlatformToolWidget::Construct(const FArguments& InArgs) +{ + auto logTextBox = SNew(SMultiLineEditableTextBox).IsReadOnly(true); + ToolConsoleLog = logTextBox; + + auto mainVerticalBox = SNew(SVerticalBox); + GeneralSettingsBox = mainVerticalBox; + + auto buttonToolbarBox = SNew(SHorizontalBox); + ButtonToolbar = buttonToolbarBox; + + auto optionalSettings = SNew(SVerticalBox); + OptionalSettings = optionalSettings; + + auto expansionFilesSettings = SNew(SVerticalBox); + ExpansionFilesSettings = expansionFilesSettings; + + BuildGeneralSettingsBox(GeneralSettingsBox); + BuildButtonToolbar(ButtonToolbar); + BuildExpansionFileBox(ExpansionFilesSettings); + + if (PlatformSettings != NULL) + { + if (PlatformSettings->GetTargetPlatform() == (uint8)EOculusXRPlatformTarget::Rift) + { + BuildRiftOptionalFields(OptionalSettings); + } + else + { + OptionalSettings.Get()->ClearChildren(); + } + } + + FString ODHIconPath = IPluginManager::Get().FindPlugin(TEXT("OculusXR"))->GetBaseDir() / TEXT("Resources/odhIcon128.png"); + const FName BrushName(*ODHIconPath); + FSlateApplication::Get().GetRenderer()->GenerateDynamicImageResource(BrushName); + ODHIconDynamicImageBrush = MakeShareable(new FSlateDynamicImageBrush(BrushName, FVector2D(60.0f, 60.0f))); + +#if PLATFORM_MAC + FString odhLink = "https://developer.oculus.com/downloads/package/oculus-developer-hub-mac/?source=unreal"; +#else + FString odhLink = "https://developer.oculus.com/downloads/package/oculus-developer-hub-win/?source=unreal"; +#endif + + ChildSlot + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.LightGroupBorder")) + .Padding(2) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot().Padding(0, 0).FillHeight(1.f) + [ + SNew(SScrollBox) + + SScrollBox::Slot() + [ + SNew(SExpandableArea) + .HeaderPadding(5) + .Padding(5) + .BorderBackgroundColor(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) + .BodyBorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .BodyBorderBackgroundColor(FLinearColor::White) + .InitiallyCollapsed(false) + .HeaderContent() + [ + SNew(SRichTextBlock) + .TextStyle(FAppStyle::Get(), "ToolBar.Heading") + .DecoratorStyleSet(&FAppStyle::Get()).AutoWrapText(true) + .Text(LOCTEXT("GeneralSettings", "<RichTextBlock.Bold>General Settings</>")) + ] + .BodyContent() + [ + mainVerticalBox + ] + ] + + SScrollBox::Slot() + [ + SNew(SExpandableArea) + .HeaderPadding(5) + .Padding(5) + .BorderBackgroundColor(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) + .BodyBorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .BodyBorderBackgroundColor(FLinearColor::White) + .InitiallyCollapsed(true) + .HeaderContent() + [ + SNew(SRichTextBlock) + .TextStyle(FAppStyle::Get(), "ToolBar.Heading") + .DecoratorStyleSet(&FAppStyle::Get()).AutoWrapText(true) + .Text(LOCTEXT("OptionalSettings", "<RichTextBlock.Bold>Optional Settings</>")) + ] + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot().AutoHeight() + [ + optionalSettings + ] + ] + ] + + SScrollBox::Slot() + [ + SNew(SExpandableArea) + .HeaderPadding(5) + .Padding(5) + .BorderBackgroundColor(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) + .BodyBorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .BodyBorderBackgroundColor(FLinearColor::White) + .InitiallyCollapsed(true) + .HeaderContent() + [ + SNew(SRichTextBlock) + .TextStyle(FAppStyle::Get(), "ToolBar.Heading") + .DecoratorStyleSet(&FAppStyle::Get()).AutoWrapText(true) + .Text(LOCTEXT("ExpansionFileSettings", "<RichTextBlock.Bold>Expansion Files</>")) + ] + .BodyContent() + [ + SNew(SVerticalBox) + + SVerticalBox::Slot().AutoHeight() + [ + expansionFilesSettings + ] + ] + ] + ] + + SVerticalBox::Slot().AutoHeight() + [ + buttonToolbarBox + ] + + SVerticalBox::Slot().FillHeight(1.f) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + [ + logTextBox + ] + ] + + SVerticalBox::Slot().AutoHeight().Padding(2.0f) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SBox) + .WidthOverride(60.0f) + .HeightOverride(60.0f) + [ + SNew(SImage) + .Image(ODHIconDynamicImageBrush.IsValid() ? ODHIconDynamicImageBrush.Get() : nullptr) + ] + ] + + SHorizontalBox::Slot().FillWidth(1.0f) + [ + SNew(SVerticalBox) + + SVerticalBox::Slot().AutoHeight().Padding(2.0f) + [ + SNew(SRichTextBlock) + .Text(LOCTEXT("ODHCallout", + "<RichTextBlock.Bold>Oculus Developer Hub</> is a desktop companion tool that can upload builds, manage apps and reduce friction in daily Quest development.")) + .DecoratorStyleSet(&FAppStyle::Get()) + .AutoWrapText(true) + ] + + SVerticalBox::Slot().AutoHeight() + [ + SNew(SBox) + .HAlign(HAlign_Left) + [ + SNew(SHyperlinkLaunchURL, odhLink) + .Text(LOCTEXT("ODHDownloadPage", "Download Oculus Developer Hub")) + .ToolTipText(LOCTEXT("ODHDownloadPageTooltip", "Opens a page that provides the download link for Oculus Developer Hub")) + ] + ] + ] + ] + ] + ] + ]; +} + +void SOculusPlatformToolWidget::BuildGeneralSettingsBox(TSharedPtr<SVerticalBox> box) +{ + if (PlatformSettings == NULL) + { + return; + } + + box.Get()->ClearChildren(); + + BuildTextComboBoxField(GeneralSettingsBox, LOCTEXT("TargetPlatform", "Target Platform"), + &OculusPlatforms, OculusPlatforms[PlatformSettings->GetTargetPlatform()], + &SOculusPlatformToolWidget::OnPlatformSettingChanged); + + // Build field for Oculus Application ID. + BuildTextField(box, LOCTEXT("AppID", "Oculus Application ID"), FText::FromString(PlatformSettings->GetApplicationID()), + LOCTEXT("AppIDTT", "Specifies the ID of your app. Obtained from the API tab of your app in the Oculus Dashboard."), + &SOculusPlatformToolWidget::OnApplicationIDChanged); + + // Build field for Oculus Application Token. + BuildTextField(box, LOCTEXT("AppToken", "Oculus Application Token"), FText::FromString(PlatformSettings->GetApplicationToken()), + LOCTEXT("AppTokenTT", "Specifies the app secret token. Obtained from the API tab of your app in the Oculus Dashboard."), + &SOculusPlatformToolWidget::OnApplicationTokenChanged, true); + + // Build field for Release Channel. + BuildTextField(box, LOCTEXT("ReleaseChannel", "Release Channel"), FText::FromString(PlatformSettings->GetReleaseChannel()), + LOCTEXT("ReleaseChannelTT", "Specifies the release channel for uploading the build. Release channel names are not case-sensitive."), + &SOculusPlatformToolWidget::OnReleaseChannelChanged); + + // Build field for Release Notes. + BuildTextField(box, LOCTEXT("ReleaseNote", "Release Note"), FText::FromString(PlatformSettings->GetReleaseNote()), + LOCTEXT("ReleaseNoteTT", "Specifies the release note text shown to users."), + &SOculusPlatformToolWidget::OnReleaseNoteChanged); + + // Platform specific fields. + if (PlatformSettings->GetTargetPlatform() == (uint8)EOculusXRPlatformTarget::Rift) + { + // Build field for Rift Build Directory. + BuildFileDirectoryField(box, LOCTEXT("BuildPath", "Rift Build Directory"), FText::FromString(PlatformSettings->OculusRiftBuildDirectory), + LOCTEXT("BuildPathTT", "Specifies the full path to the directory containing your build files."), + &SOculusPlatformToolWidget::OnSelectRiftBuildDirectory, &SOculusPlatformToolWidget::OnClearRiftBuildDirectory); + + // Build field for Build Version. + BuildTextField(box, LOCTEXT("BuildVersion", "Build Version"), FText::FromString(PlatformSettings->OculusRiftBuildVersion), + LOCTEXT("BuildVersionTT", "Specifies the version number shown to users."), + &SOculusPlatformToolWidget::OnRiftBuildVersionChanged); + + // Build field for Launch File Path. + BuildFileDirectoryField(box, LOCTEXT("LaunchPath", "Launch File Path"), FText::FromString(PlatformSettings->GetLaunchFilePath()), + LOCTEXT("LaunchPathTT", " Specifies the path to the executable that launches your app."), + &SOculusPlatformToolWidget::OnSelectLaunchFilePath, &SOculusPlatformToolWidget::OnClearLaunchFilePath); + } + else + { + // Build field for APK File Path. + BuildFileDirectoryField(box, LOCTEXT("APKLaunchPath", "APK File Path"), FText::FromString(PlatformSettings->GetLaunchFilePath()), + LOCTEXT("APKLaunchPathTT", " Specifies the path to the APK that launches your app."), + &SOculusPlatformToolWidget::OnSelectLaunchFilePath, &SOculusPlatformToolWidget::OnClearLaunchFilePath); + + BuildCheckBoxField(box, LOCTEXT("UploadDebugSymbols", "Upload Debug Symbols"), PlatformSettings->UploadDebugSymbols, + LOCTEXT("UploadDebugSymbolsTT", "If checked, debug symbols will be uploaded along with the application."), + &SOculusPlatformToolWidget::OnUploadDebugSymbolsChanged); + + if (PlatformSettings->UploadDebugSymbols) + { + if (bShowUploadDebugSymbols != PlatformSettings->UploadDebugSymbols) + { + if (PlatformSettings->GetSymbolDirPath().IsEmpty()) + { + FString defaultPath = GenerateSymbolPath(); + PlatformSettings->SetSymbolDirPath(FPaths::ConvertRelativePathToFull(defaultPath)); + PlatformSettings->SaveConfig(); + } + } + + // Build field for Debug symbol directory path. + BuildFileDirectoryField(box, LOCTEXT("SymbolPath", "Symbol Directory Path"), FText::FromString(PlatformSettings->GetSymbolDirPath()), + LOCTEXT("SymbolPathTT", "Specifies the path to the directory containing the app symbols (libUE4.so)."), + &SOculusPlatformToolWidget::OnSelectSymbolDirPath, &SOculusPlatformToolWidget::OnClearSymbolDirPath, 1); + + BuildCheckBoxField(box, LOCTEXT("DebugSymbolsOnly", "Upload Debug Symbols Only"), PlatformSettings->DebugSymbolsOnly, + LOCTEXT("DebugSymbolsOnlyTT", "If checked, the tool will upload onyl debug symbols to an existing build. Requires Build ID, App ID, App Token, and Debug Symbols Directory."), + &SOculusPlatformToolWidget::OnDebugSymbolsOnlyChanged, 1); + + if (PlatformSettings->DebugSymbolsOnly) + { + BuildTextField(box, LOCTEXT("BuildID", "Build ID"), FText::FromString(PlatformSettings->BuildID), + LOCTEXT("BuildIDTT", "Specifies the Build ID to upload debug symbols to."), + &SOculusPlatformToolWidget::OnBuildIDChanged, false, 1); + } + } + bShowUploadDebugSymbols = PlatformSettings->UploadDebugSymbols; + } +} + +void SOculusPlatformToolWidget::BuildTextField(TSharedPtr<SVerticalBox> box, FText name, FText text, FText tooltip, + PTextComittedDel deleg, bool isPassword, int32 indentAmount) +{ + FMargin textMargin = FMargin(TEXT_INDENT_OFFSET * indentAmount, 1.0f, 1.0f, 1.0f); + + box.Get()->AddSlot() + .Padding(1) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1).AutoWidth() + [ + SNew(SBox) + .WidthOverride(250.f) + .Padding(textMargin) + [ + SNew(STextBlock) + .Text(name) + .ToolTipText(tooltip) + ] + ] + + SHorizontalBox::Slot().Padding(1).FillWidth(1.f) + [ + SNew(SEditableTextBox) + .Text(text) + .IsPassword(isPassword) + .OnTextCommitted(this, deleg) + ] + ]; +} + +void SOculusPlatformToolWidget::BuildTextComboBoxField(TSharedPtr<SVerticalBox> box, FText name, + TArray<TSharedPtr<FString>>* options, TSharedPtr<FString> current, PTextComboBoxDel deleg, int32 indentAmount) +{ + FMargin textMargin = FMargin(TEXT_INDENT_OFFSET * indentAmount, 1.0f, 1.0f, 1.0f); + + box.Get()->AddSlot() + .Padding(1) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1).AutoWidth() + [ + SNew(SBox) + .WidthOverride(250.f).Padding(textMargin) + [ + SNew(SRichTextBlock) + .DecoratorStyleSet(&FAppStyle::Get()) + .Text(name) + ] + ] + + SHorizontalBox::Slot().Padding(1).FillWidth(1.f) + [ + SNew(STextComboBox) + .OptionsSource(options) + .InitiallySelectedItem(current) + .OnSelectionChanged(this, deleg) + ] + ]; +} + +void SOculusPlatformToolWidget::BuildCheckBoxField(TSharedPtr<SVerticalBox> box, FText name, bool check, + FText tooltip, PCheckBoxChangedDel deleg, int32 indentAmount) +{ + FMargin textMargin = FMargin(TEXT_INDENT_OFFSET * indentAmount, 1.0f, 1.0f, 1.0f); + + box.Get()->AddSlot() + .Padding(1) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1).AutoWidth() + [ + SNew(SBox) + .WidthOverride(250.f).Padding(textMargin) + [ + SNew(SRichTextBlock) + .DecoratorStyleSet(&FAppStyle::Get()) + .Text(name) + ] + ] + + SHorizontalBox::Slot().Padding(1).FillWidth(1.f) + [ + SNew(SCheckBox) + .OnCheckStateChanged(this, deleg) + .IsChecked(check ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + ] + ]; +} + +void SOculusPlatformToolWidget::BuildFileDirectoryField(TSharedPtr<SVerticalBox> box, FText name, FText path, FText tooltip, + PButtonClickedDel deleg, PButtonClickedDel clearDeleg, int32 indentAmount) +{ + EVisibility cancelButtonVisibility = path.IsEmpty() ? EVisibility::Hidden : EVisibility::Visible; + FMargin textMargin = FMargin(TEXT_INDENT_OFFSET * indentAmount, 1.0f, 1.0f, 1.0f); + + box.Get()->AddSlot() + .Padding(1) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1).AutoWidth() + [ + SNew(SBox) + .WidthOverride(250.f).Padding(textMargin) + [ + SNew(STextBlock) + .Text(name) + .ToolTipText(tooltip) + ] + ] + + SHorizontalBox::Slot().Padding(1).FillWidth(1.f) + [ + SNew(SEditableText) + .Text(path) + .IsReadOnly(true) + .Justification(ETextJustify::Left) + ] + + SHorizontalBox::Slot().Padding(1).AutoWidth().HAlign(EHorizontalAlignment::HAlign_Right) + [ + SNew(SButton) + .Text(FText::FromString("X")) + .Visibility(cancelButtonVisibility) + .OnClicked(this, clearDeleg) + .ButtonColorAndOpacity(FLinearColor(0.36f, 0.1f, 0.05f)) + ] + + SHorizontalBox::Slot().Padding(1).AutoWidth().HAlign(EHorizontalAlignment::HAlign_Right) + [ + SNew(SButton) + .Text((LOCTEXT("Choose", "Choose..."))) + .OnClicked(this, deleg) + ] + ]; +} + +void SOculusPlatformToolWidget::BuildButtonToolbar(TSharedPtr<SHorizontalBox> box) +{ + box.Get()->ClearChildren(); + + box.Get()->AddSlot().FillWidth(1.f); + box.Get()->AddSlot().AutoWidth().Padding(2.f) + [ + SNew(SButton) + .Text((LOCTEXT("Upload", "Upload"))) + .OnClicked(this, &SOculusPlatformToolWidget::OnStartPlatformUpload) + .IsEnabled(ActiveUploadButton) + ]; + box.Get()->AddSlot().AutoWidth().Padding(2.f) + [ + SNew(SButton) + .Text((LOCTEXT("Cancel", "Cancel"))) + .OnClicked(this, &SOculusPlatformToolWidget::OnCancelUpload) + .IsEnabled(!ActiveUploadButton) + ]; + box.Get()->AddSlot().FillWidth(1.f); +} + +void SOculusPlatformToolWidget::BuildRiftOptionalFields(TSharedPtr<SVerticalBox> box) +{ + if (PlatformSettings == NULL) + { + return; + } + + box.Get()->ClearChildren(); + + // Add Launch Parameter Field + BuildTextField(box, LOCTEXT("LaunchParams", "Launch Parameters"), FText::FromString(PlatformSettings->OculusRiftLaunchParams), + LOCTEXT("LaunchParamsTT", ""), + &SOculusPlatformToolWidget::OnRiftLaunchParamsChanged); + + // Add Firewall Exception Toggle + BuildCheckBoxField(box, LOCTEXT("Firewall", "Firewall Exception"), PlatformSettings->OculusRiftFireWallException, + LOCTEXT("FirewallTT", ""), + &SOculusPlatformToolWidget::OnRiftFirewallChanged); + + // Add Gamepad Emulation Dropdown + BuildTextComboBoxField(box, LOCTEXT("GamepadEmu", "Gamepad Emulation"), + &RiftGamepadEmulation, RiftGamepadEmulation[(uint8)PlatformSettings->GetRiftGamepadEmulation()], + &SOculusPlatformToolWidget::OnRiftGamepadEmulationChanged); + + // Generate 2D Settings Expandable Area + TSharedRef<SVerticalBox> settings2DBox = SNew(SVerticalBox); + + // Add 2D Launch File Field + BuildFileDirectoryField(settings2DBox, LOCTEXT("2DLaunch", "2D Launch File"), FText::FromString(PlatformSettings->OculusRift2DLaunchPath), + LOCTEXT("2DLaunchPathTT", ""), + &SOculusPlatformToolWidget::OnSelect2DLaunchPath, &SOculusPlatformToolWidget::OnClear2DLaunchPath); + + // Add 2D Launch Parameter Field + BuildTextField(settings2DBox, LOCTEXT("2DLaunchParams", "2D Launch Parameters"), FText::FromString(PlatformSettings->OculusRift2DLaunchParams), + LOCTEXT("2DLaunchParamsTT", ""), + &SOculusPlatformToolWidget::On2DLaunchParamsChanged); + + box.Get()->AddSlot().AutoHeight().Padding(1) + [ + SNew(SExpandableArea) + .HeaderPadding(5) + .Padding(5) + .BorderBackgroundColor(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) + .BodyBorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .BodyBorderBackgroundColor(FLinearColor::White) + .InitiallyCollapsed(Options2DCollapsed) + .OnAreaExpansionChanged(this, &SOculusPlatformToolWidget::On2DOptionsExpanded) + .HeaderContent() + [ + SNew(SRichTextBlock) + .TextStyle(FAppStyle::Get(), "ToolBar.Heading") + .DecoratorStyleSet(&FAppStyle::Get()).AutoWrapText(true) + .Text(LOCTEXT("2DSettings", "<RichTextBlock.Bold>2D Settings</>")) + ] + .BodyContent() + [ + settings2DBox + ] + ]; + + BuildRedistPackagesBox(box); +} + +void SOculusPlatformToolWidget::BuildRedistPackagesBox(TSharedPtr<SVerticalBox> box) +{ + // Create check box toggle for each redistributable package we loaded + TSharedRef<SVerticalBox> redistBox = SNew(SVerticalBox); + for (int i = 0; i < PlatformSettings->OculusRedistPackages.Num(); i++) + { + FOculusXRRedistPackage* Package = &PlatformSettings->OculusRedistPackages[i]; + redistBox->AddSlot() + .Padding(1) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1).AutoWidth() + [ + SNew(SBox) + .WidthOverride(250.f) + [ + SNew(SRichTextBlock) + .DecoratorStyleSet(&FAppStyle::Get()) + .Text(FText::FromString(Package->Name)) + ] + ] + + SHorizontalBox::Slot().Padding(1).FillWidth(1.f) + [ + SNew(SCheckBox) + .OnCheckStateChanged(this, &SOculusPlatformToolWidget::OnRedistPackageStateChanged, Package) + .IsChecked(Package->Included ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + ] + ]; + } + + box.Get()->AddSlot().AutoHeight().Padding(1) + [ + SNew(SExpandableArea) + .HeaderPadding(5) + .Padding(5) + .BorderBackgroundColor(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) + .BodyBorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .BodyBorderBackgroundColor(FLinearColor::White) + .InitiallyCollapsed(OptionsRedistPackagesCollapsed) + .OnAreaExpansionChanged(this, &SOculusPlatformToolWidget::OnRedistPackagesExpanded) + .HeaderContent() + [ + SNew(SRichTextBlock) + .TextStyle(FAppStyle::Get(), "ToolBar.Heading") + .DecoratorStyleSet(&FAppStyle::Get()).AutoWrapText(true) + .Text(LOCTEXT("RedistPack", "<RichTextBlock.Bold>Redistributable Packages</>")) + ] + .BodyContent() + [ + redistBox + ] + ]; +} + +void SOculusPlatformToolWidget::BuildExpansionFileBox(TSharedPtr<SVerticalBox> box) +{ + if (PlatformSettings == NULL) + { + return; + } + + ExpansionFilesSettings.Get()->ClearChildren(); + + if (PlatformSettings->GetTargetPlatform() == (uint8)EOculusXRPlatformTarget::Rift) + { + BuildFileDirectoryField(box, LOCTEXT("LanguagePacks", "Language Packs Directory"), FText::FromString(PlatformSettings->GetLanguagePacksPath()), + LOCTEXT("LanguagePacksTT", ""), &SOculusPlatformToolWidget::OnSelectLanguagePacksPath, &SOculusPlatformToolWidget::OnClearLanguagePacksPath); + } + + BuildFileDirectoryField(box, LOCTEXT("ExpansionFilesDirectory", "Expansion Files Directory"), FText::FromString(PlatformSettings->GetExpansionFilesPath()), + LOCTEXT("ExpansionFilesTT", ""), &SOculusPlatformToolWidget::OnSelectExpansionFilesPath, &SOculusPlatformToolWidget::OnClearExpansionFilesPath); + + TArray<FOculusXRAssetConfig>* AssetConfigs = PlatformSettings->GetAssetConfigs(); + if (AssetConfigs) + { + for (int i = 0; i < AssetConfigs->Num(); i++) + { + auto AssetConfigBox = SNew(SVerticalBox); + BuildAssetConfigBox(AssetConfigBox, (*AssetConfigs)[i], i); + + box.Get()->AddSlot().AutoHeight().Padding(1) + [ + SNew(SExpandableArea) + .HeaderPadding(5) + .Padding(5) + .BorderBackgroundColor(FLinearColor(0.4f, 0.4f, 0.4f, 1.0f)) + .BodyBorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + .BodyBorderBackgroundColor(FLinearColor::White) + .HeaderContent() + [ + SNew(SRichTextBlock) + .TextStyle(FAppStyle::Get(), "ToolBar.Heading") + .DecoratorStyleSet(&FAppStyle::Get()).AutoWrapText(true) + .Text(FText::FromString((*AssetConfigs)[i].Name)) + ] + .BodyContent() + [ + AssetConfigBox + ] + ]; + } + } +} + +void SOculusPlatformToolWidget::BuildAssetConfigBox(TSharedPtr<SVerticalBox> box, FOculusXRAssetConfig config, int index) +{ + box.Get()->AddSlot() + .Padding(1) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1).AutoWidth() + [ + SNew(SBox) + .WidthOverride(250.f) + [ + SNew(SRichTextBlock) + .DecoratorStyleSet(&FAppStyle::Get()) + .Text(LOCTEXT("AssetType", "Asset Type")) + ] + ] + + SHorizontalBox::Slot().Padding(1).FillWidth(1.f) + [ + SNew(STextComboBox) + .OptionsSource(&AssetType) + .InitiallySelectedItem(AssetType[(uint8)config.AssetType]) + .OnSelectionChanged(this, &SOculusPlatformToolWidget::OnAssetConfigTypeChanged, index) + ] + ]; + + box.Get()->AddSlot() + .Padding(1) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1).AutoWidth() + [ + SNew(SBox) + .WidthOverride(250.f) + [ + SNew(SRichTextBlock) + .DecoratorStyleSet(&FAppStyle::Get()) + .Text(LOCTEXT("AssetRequired", "Required")) + ] + ] + + SHorizontalBox::Slot().Padding(1).FillWidth(1.f) + [ + SNew(SCheckBox) + .OnCheckStateChanged(this, &SOculusPlatformToolWidget::OnAssetConfigRequiredChanged, index) + .IsChecked(config.Required ? ECheckBoxState::Checked : ECheckBoxState::Unchecked) + ] + ]; + + box.Get()->AddSlot() + .Padding(1) + .AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().Padding(1).AutoWidth() + [ + SNew(SBox) + .WidthOverride(250.f) + [ + SNew(STextBlock) + .Text(LOCTEXT("SKU", "SKU")) + ] + ] + + SHorizontalBox::Slot().Padding(1).FillWidth(1.f) + [ + SNew(SEditableTextBox) + .Text(FText::FromString(config.Sku)) + .OnTextCommitted(this, &SOculusPlatformToolWidget::OnAssetConfigSKUChanged, index) + ] + ]; +} + +bool SOculusPlatformToolWidget::ConstructArguments(FString& args) +{ + if (PlatformSettings == NULL) + { + return false; + } + + if (PlatformSettings->UploadDebugSymbols && PlatformSettings->DebugSymbolsOnly) + { + return ConstructDebugSymbolArguments(args); + } + + // Build the args string that will be passed to the CLI. Print all errors that occur to the log. + bool success = true; + + switch (PlatformSettings->GetTargetPlatform()) + { + case (uint8)EOculusXRPlatformTarget::Rift: + args = "upload-rift-build"; + break; + case (uint8)EOculusXRPlatformTarget::Quest: + args = "upload-quest-build"; + break; + default: + UpdateLogText(LogText + "ERROR: Invalid target platform selected"); + success = false; + break; + } + + // Oculus Application ID check and command. + ValidateTextField(&SOculusPlatformToolWidget::IDFieldValidator, PlatformSettings->GetApplicationID(), + LOCTEXT("ApplicationID", "Application ID").ToString(), success); + args += " --app_id \"" + PlatformSettings->GetApplicationID() + "\""; + + // Oculus Application Token check and command. + ValidateTextField(&SOculusPlatformToolWidget::GenericFieldValidator, PlatformSettings->GetApplicationToken(), + LOCTEXT("ApplicationToken", "Application Token").ToString(), success); + args += " --app_secret \"" + PlatformSettings->GetApplicationToken() + "\""; + + // Release Channel check and command. + ValidateTextField(&SOculusPlatformToolWidget::GenericFieldValidator, PlatformSettings->GetReleaseChannel(), + LOCTEXT("ReleaseChannel", "Release Channel").ToString(), success); + args += " --channel \"" + PlatformSettings->GetReleaseChannel() + "\""; + + // Release Note check and command. Not a required command. + if (!PlatformSettings->GetReleaseNote().IsEmpty()) + { + FString SanatizedReleaseNote = PlatformSettings->GetReleaseNote(); + SanatizedReleaseNote = SanatizedReleaseNote.Replace(TEXT("\""), TEXT("\"\"")); + args += " --notes \"" + SanatizedReleaseNote + "\""; + } + + // Platform specific commands + if (PlatformSettings->GetTargetPlatform() == (uint8)EOculusXRPlatformTarget::Rift) + { + // Launch File Path check and command. + ValidateTextField(&SOculusPlatformToolWidget::FileFieldValidator, PlatformSettings->GetLaunchFilePath(), + LOCTEXT("LaunchFile", "Launch File Path").ToString(), success); + args += " --launch-file \"" + PlatformSettings->GetLaunchFilePath() + "\""; + + // Rift Build Directory check and command. + ValidateTextField(&SOculusPlatformToolWidget::DirectoryFieldValidator, PlatformSettings->OculusRiftBuildDirectory, + LOCTEXT("RiftBuildDir", "Rift Build Directory").ToString(), success); + args += " --build_dir \"" + PlatformSettings->OculusRiftBuildDirectory + "\""; + + // Rift Build Version check and command. + ValidateTextField(&SOculusPlatformToolWidget::GenericFieldValidator, PlatformSettings->OculusRiftBuildVersion, + LOCTEXT("BuildVersion", "Build Version").ToString(), success); + args += " --version \"" + PlatformSettings->OculusRiftBuildVersion + "\""; + + // Rift Launch Parameters check and command + if (!PlatformSettings->OculusRiftLaunchParams.IsEmpty()) + { + ValidateTextField(&SOculusPlatformToolWidget::LaunchParamValidator, PlatformSettings->OculusRiftLaunchParams, + LOCTEXT("LaunchParam", "Launch Parameters").ToString(), success); + args += " --launch_params \"" + PlatformSettings->OculusRiftLaunchParams + "\""; + } + + // Rift 2D Options checks and commands + if (!PlatformSettings->OculusRift2DLaunchPath.IsEmpty()) + { + ValidateTextField(&SOculusPlatformToolWidget::FileFieldValidator, PlatformSettings->OculusRift2DLaunchPath, + LOCTEXT("2DLaunchFile", "2D Launch File Path").ToString(), success); + args += " --launch_file_2d \"" + PlatformSettings->OculusRift2DLaunchPath + "\""; + + if (!PlatformSettings->OculusRift2DLaunchParams.IsEmpty()) + { + ValidateTextField(&SOculusPlatformToolWidget::LaunchParamValidator, PlatformSettings->OculusRift2DLaunchParams, + LOCTEXT("2DLaunchParams", "2D Launch Parameters").ToString(), success); + args += " --launch_params_2d \"" + PlatformSettings->OculusRift2DLaunchParams + "\""; + } + } + + // Rift Firewall Exception command + if (PlatformSettings->OculusRiftFireWallException) + { + args += " --firewall_exceptions true"; + } + + // Rift Gamepad Emulation command + if (PlatformSettings->GetRiftGamepadEmulation() > EOculusXRGamepadEmulation::Off && + PlatformSettings->GetRiftGamepadEmulation() < EOculusXRGamepadEmulation::Length) + { + args += " --gamepad-emulation "; + switch (PlatformSettings->GetRiftGamepadEmulation()) + { + case EOculusXRGamepadEmulation::Twinstick: args += "TWINSTICK"; break; + case EOculusXRGamepadEmulation::RightDPad: args += "RIGHT_D_PAD"; break; + case EOculusXRGamepadEmulation::LeftDPad: args += "LEFT_D_PAD"; break; + default: args += "OFF"; break; + } + } + + // Rift Redistributable Packages commands + TArray<FString> IncludedPackages; + for (int i = 0; i < PlatformSettings->OculusRedistPackages.Num(); i++) + { + FOculusXRRedistPackage Package = PlatformSettings->OculusRedistPackages[i]; + if (Package.Included) + { + IncludedPackages.Add(Package.Id); + } + } + if (IncludedPackages.Num() > 0) + { + args += " --redistributables \"" + FString::Join(IncludedPackages, TEXT(",")) + "\""; + } + } + else + { + // APK File Path check and command. + ValidateTextField(&SOculusPlatformToolWidget::FileFieldValidator, PlatformSettings->GetLaunchFilePath(), + LOCTEXT("APKLaunchFile", "APK File Path").ToString(), success); + args += " --apk \"" + PlatformSettings->GetLaunchFilePath() + "\""; + + if (PlatformSettings->UploadDebugSymbols) + { + ValidateTextField(&SOculusPlatformToolWidget::DirectoryFieldValidator, PlatformSettings->GetSymbolDirPath(), + LOCTEXT("SymbolDirPath", "Symbol Directory Path").ToString(), success); + if (success) + { + args += " --debug-symbols-dir \"" + PlatformSettings->GetSymbolDirPath() + "\""; + } + } + } + + if (!PlatformSettings->GetExpansionFilesPath().IsEmpty()) + { + ValidateTextField(&SOculusPlatformToolWidget::DirectoryFieldValidator, PlatformSettings->GetExpansionFilesPath(), + LOCTEXT("ExpansionFilesPath", "Expansion Files Path").ToString(), success); + args += " --assets-dir \"" + PlatformSettings->GetExpansionFilesPath() + "\""; + + TArray<FOculusXRAssetConfig>* AssetConfigs = PlatformSettings->GetAssetConfigs(); + if (AssetConfigs->Num() > 0) + { + TArray<FString> AssetConfig; + for (int i = 0; i < AssetConfigs->Num(); i++) + { + TArray<FString> ConfigParams; + FOculusXRAssetConfig Config = (*AssetConfigs)[i]; + + if (Config.Required) + { + ConfigParams.Add("\\\"required\\\":true"); + } + if (Config.AssetType > EOculusXRAssetType::Default && Config.AssetType < EOculusXRAssetType::Length) + { + FString command = "\\\"type\\\":"; + switch (Config.AssetType) + { + case EOculusXRAssetType::Store: + ConfigParams.Add(command + "\\\"STORE\\\""); + break; + case EOculusXRAssetType::Language_Pack: + ConfigParams.Add(command + "\\\"LANGUAGE_PACK\\\""); + break; + default: + ConfigParams.Add(command + "\\\"DEFAULT\\\""); + break; + } + } + if (!Config.Sku.IsEmpty()) + { + ConfigParams.Add("\\\"sku\\\":\\\"" + Config.Sku + "\\\""); + } + + if (ConfigParams.Num() > 0) + { + FString ConfigCommand = "\\\"" + Config.Name + "\\\":{" + FString::Join(ConfigParams, TEXT(",")) + "}"; + AssetConfig.Add(ConfigCommand); + } + } + + if (AssetConfig.Num()) + { + args += " --asset_files_config {" + FString::Join(AssetConfig, TEXT(",")) + "}"; + } + } + } + UE_LOG(LogTemp, Warning, TEXT("%s"), *args); + return success; +} + +bool SOculusPlatformToolWidget::ConstructDebugSymbolArguments(FString& args) +{ + bool success = true; + args = "upload-debug-symbols"; + + ValidateTextField(&SOculusPlatformToolWidget::IDFieldValidator, PlatformSettings->BuildID, + LOCTEXT("BuildID", "Build ID").ToString(), success); + args += " --parent \"" + PlatformSettings->BuildID + "\""; + + // Oculus Application ID check and command. + ValidateTextField(&SOculusPlatformToolWidget::IDFieldValidator, PlatformSettings->GetApplicationID(), + LOCTEXT("ApplicationID", "Application ID").ToString(), success); + args += " --app_id \"" + PlatformSettings->GetApplicationID() + "\""; + + // Oculus Application Token check and command. + ValidateTextField(&SOculusPlatformToolWidget::GenericFieldValidator, PlatformSettings->GetApplicationToken(), + LOCTEXT("ApplicationToken", "Application Token").ToString(), success); + args += " --app_secret \"" + PlatformSettings->GetApplicationToken() + "\""; + + ValidateTextField(&SOculusPlatformToolWidget::DirectoryFieldValidator, PlatformSettings->GetSymbolDirPath(), + LOCTEXT("SymbolDirPath", "Symbol Directory Path").ToString(), success); + args += " --debug-symbols-dir \"" + PlatformSettings->GetSymbolDirPath() + "\""; + args += " --debug-symbols-pattern \"*.so\""; + + return success; +} + +void SOculusPlatformToolWidget::EnableUploadButton(bool enabled) +{ + RequestUploadButtonActive = enabled; +} + +void SOculusPlatformToolWidget::LoadConfigSettings() +{ + PlatformSettings = GetMutableDefault<UOculusXRPlatformToolSettings>(); + PlatformEnum = StaticEnum<EOculusXRPlatformTarget>(); + GamepadEmulationEnum = StaticEnum<EOculusXRGamepadEmulation>(); + AssetTypeEnum = StaticEnum<EOculusXRAssetType>(); + + RiftGamepadEmulation.Empty(); + OculusPlatforms.Empty(); + for (uint8 i = 0; i < (uint8)EOculusXRPlatformTarget::Length; i++) + { + OculusPlatforms.Add(MakeShareable(new FString(PlatformEnum->GetDisplayNameTextByIndex((int64)i).ToString()))); + } + for (uint8 i = 0; i < (uint8)EOculusXRGamepadEmulation::Length; i++) + { + RiftGamepadEmulation.Add(MakeShareable(new FString(GamepadEmulationEnum->GetDisplayNameTextByIndex((int64)i).ToString()))); + } + for (uint8 i = 0; i < (uint8)EOculusXRAssetType::Length; i++) + { + AssetType.Add(MakeShareable(new FString(AssetTypeEnum->GetDisplayNameTextByIndex((int64)i).ToString()))); + } + + LoadRedistPackages(); +} + +void SOculusPlatformToolWidget::LoadRedistPackages() +{ + (new FAsyncTask<FPlatformLoadRedistPackagesTask>(UpdateLogTextDel))->StartBackgroundTask(); +} + +FReply SOculusPlatformToolWidget::OnStartPlatformUpload() +{ + FString launchArgs; + + UpdateLogText(""); + FOculusXRHMDModule::GetPluginWrapper().SendEvent2("oculus_platform_tool", "upload", "integration"); + if (ConstructArguments(launchArgs)) + { + UpdateLogText(LogText + LOCTEXT("StartUpload", "Starting Platform Tool Upload Process . . .\n").ToString()); + (new FAsyncTask<FPlatformUploadTask>(launchArgs, EnableUploadButtonDel, UpdateLogTextDel, SetProcessDel))->StartBackgroundTask(); + } + return FReply::Handled(); +} + +void SOculusPlatformToolWidget::OnPlatformSettingChanged(TSharedPtr<FString> ItemSelected, ESelectInfo::Type SelectInfo) +{ + if (!ItemSelected.IsValid()) + { + return; + } + + for (uint8 i = 0; i < (uint8)EOculusXRPlatformTarget::Length; i++) + { + if (PlatformEnum->GetDisplayNameTextByIndex(i).EqualTo(FText::FromString(*ItemSelected))) + { + if (PlatformSettings != NULL) + { + PlatformSettings->SetTargetPlatform(i); + PlatformSettings->SaveConfig(); + + LoadConfigSettings(); + BuildGeneralSettingsBox(GeneralSettingsBox); + BuildExpansionFileBox(ExpansionFilesSettings); + + OptionalSettings.Get()->ClearChildren(); + if (i == (uint8)EOculusXRPlatformTarget::Rift) + { + BuildRiftOptionalFields(OptionalSettings); + } + break; + } + } + } +} + +void SOculusPlatformToolWidget::OnApplicationIDChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->SetApplicationID(InText.ToString()); + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::OnApplicationTokenChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->SetApplicationToken(InText.ToString()); + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::OnReleaseChannelChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->SetReleaseChannel(InText.ToString()); + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::OnReleaseNoteChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->SetReleaseNote(InText.ToString()); + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::OnRiftBuildVersionChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->OculusRiftBuildVersion = InText.ToString(); + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::OnRiftLaunchParamsChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->OculusRiftLaunchParams = InText.ToString(); + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::On2DLaunchParamsChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->OculusRift2DLaunchParams = InText.ToString(); + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::OnRiftFirewallChanged(ECheckBoxState CheckState) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->OculusRiftFireWallException = CheckState == ECheckBoxState::Checked ? true : false; + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::OnRedistPackageStateChanged(ECheckBoxState CheckState, FOculusXRRedistPackage* Package) +{ + if (PlatformSettings != NULL) + { + Package->Included = CheckState == ECheckBoxState::Checked; + PlatformSettings->SaveConfig(); + BuildRiftOptionalFields(OptionalSettings); + } +} + +void SOculusPlatformToolWidget::OnAssetConfigTypeChanged(TSharedPtr<FString> ItemSelected, ESelectInfo::Type SelectInfo, int i) +{ + if (PlatformSettings != NULL) + { + TArray<FOculusXRAssetConfig>* AssetConfigs = PlatformSettings->GetAssetConfigs(); + for (int e = 0; e < (uint8)EOculusXRAssetType::Length; e++) + { + if (AssetTypeEnum->GetDisplayNameTextByIndex(e).ToString().Equals(*ItemSelected.Get())) + { + (*AssetConfigs)[i].AssetType = (EOculusXRAssetType)e; + break; + } + } + + PlatformSettings->SaveConfig(); + BuildExpansionFileBox(ExpansionFilesSettings); + } +} + +void SOculusPlatformToolWidget::OnAssetConfigRequiredChanged(ECheckBoxState CheckState, int i) +{ + if (PlatformSettings != NULL) + { + TArray<FOculusXRAssetConfig>* AssetConfigs = PlatformSettings->GetAssetConfigs(); + (*AssetConfigs)[i].Required = CheckState == ECheckBoxState::Checked; + + PlatformSettings->SaveConfig(); + BuildExpansionFileBox(ExpansionFilesSettings); + } +} + +void SOculusPlatformToolWidget::OnAssetConfigSKUChanged(const FText& InText, ETextCommit::Type InCommitType, int i) +{ + if (PlatformSettings != NULL) + { + TArray<FOculusXRAssetConfig>* AssetConfigs = PlatformSettings->GetAssetConfigs(); + (*AssetConfigs)[i].Sku = InText.ToString(); + + PlatformSettings->SaveConfig(); + BuildExpansionFileBox(ExpansionFilesSettings); + } +} + +void SOculusPlatformToolWidget::OnUploadDebugSymbolsChanged(ECheckBoxState CheckState) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->UploadDebugSymbols = CheckState == ECheckBoxState::Checked ? true : false; + PlatformSettings->SaveConfig(); + + BuildGeneralSettingsBox(GeneralSettingsBox); + } +} + +void SOculusPlatformToolWidget::OnDebugSymbolsOnlyChanged(ECheckBoxState CheckState) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->DebugSymbolsOnly = CheckState == ECheckBoxState::Checked ? true : false; + PlatformSettings->SaveConfig(); + + BuildGeneralSettingsBox(GeneralSettingsBox); + } +} + +void SOculusPlatformToolWidget::OnBuildIDChanged(const FText& InText, ETextCommit::Type InCommitType) +{ + if (PlatformSettings != NULL) + { + PlatformSettings->BuildID = InText.ToString(); + PlatformSettings->SaveConfig(); + } +} + +void SOculusPlatformToolWidget::OnRiftGamepadEmulationChanged(TSharedPtr<FString> ItemSelected, ESelectInfo::Type SelectInfo) +{ + if (!ItemSelected.IsValid()) + { + return; + } + + for (uint8 i = 0; i < (uint8)EOculusXRGamepadEmulation::Length; i++) + { + if (GamepadEmulationEnum->GetDisplayNameTextByIndex(i).EqualTo(FText::FromString(*ItemSelected))) + { + if (PlatformSettings != NULL) + { + PlatformSettings->SetRiftGamepadEmulation(i); + PlatformSettings->SaveConfig(); + break; + } + } + } +} + +FReply SOculusPlatformToolWidget::OnSelectRiftBuildDirectory() +{ + TSharedPtr<SWindow> parentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + const void* parentWindowHandle = (parentWindow.IsValid() && parentWindow->GetNativeWindow().IsValid()) ? parentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr; + + if (PlatformSettings != NULL) + { + FString path; + FString defaultPath = PlatformSettings->OculusRiftBuildDirectory.IsEmpty() ? FPaths::ProjectContentDir() : PlatformSettings->OculusRiftBuildDirectory; + if (FDesktopPlatformModule::Get()->OpenDirectoryDialog(parentWindowHandle, "Choose Rift Build Directory", defaultPath, path)) + { + PlatformSettings->OculusRiftBuildDirectory = path; + PlatformSettings->SaveConfig(); + BuildGeneralSettingsBox(GeneralSettingsBox); + } + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnClearRiftBuildDirectory() +{ + if (PlatformSettings != NULL) + { + PlatformSettings->OculusRiftBuildDirectory.Empty(); + PlatformSettings->SaveConfig(); + BuildGeneralSettingsBox(GeneralSettingsBox); + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnSelectLaunchFilePath() +{ + TSharedPtr<SWindow> parentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + const void* parentWindowHandle = (parentWindow.IsValid() && parentWindow->GetNativeWindow().IsValid()) ? parentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr; + + if (PlatformSettings != NULL) + { + TArray<FString> path; + FString defaultPath = PlatformSettings->GetLaunchFilePath().IsEmpty() ? FPaths::ProjectContentDir() : PlatformSettings->GetLaunchFilePath(); + FString fileType = PlatformSettings->GetTargetPlatform() == (uint8)EOculusXRPlatformTarget::Rift ? "Executables (*.exe)|*.exe" : "APKs (*.apk)|*.apk"; + if (FDesktopPlatformModule::Get()->OpenFileDialog(parentWindowHandle, "Choose Launch File", defaultPath, defaultPath, fileType, EFileDialogFlags::None, path)) + { + if (path.Num() > 0) + { + PlatformSettings->SetLaunchFilePath(FPaths::ConvertRelativePathToFull(path[0])); + } + PlatformSettings->SaveConfig(); + BuildGeneralSettingsBox(GeneralSettingsBox); + } + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnClearLaunchFilePath() +{ + if (PlatformSettings != NULL) + { + PlatformSettings->SetLaunchFilePath(""); + PlatformSettings->SaveConfig(); + BuildGeneralSettingsBox(GeneralSettingsBox); + } + return FReply::Handled(); +} + +FString SOculusPlatformToolWidget::GenerateSymbolPath() +{ + return FPaths::ProjectDir() + TEXT("Binaries/Android/") + FApp::GetProjectName() + TEXT("_Symbols_v1/") + FApp::GetProjectName() + TEXT("-arm64"); +} + + +FReply SOculusPlatformToolWidget::OnSelectSymbolDirPath() +{ + TSharedPtr<SWindow> parentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + const void* parentWindowHandle = (parentWindow.IsValid() && parentWindow->GetNativeWindow().IsValid()) ? parentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr; + + if (PlatformSettings != NULL) + { + FString dirPath; + FString defaultPath = PlatformSettings->GetSymbolDirPath().IsEmpty() ? GenerateSymbolPath() : PlatformSettings->GetSymbolDirPath(); + if (FDesktopPlatformModule::Get()->OpenDirectoryDialog(parentWindowHandle, "Choose Launch File", defaultPath, dirPath)) + { + PlatformSettings->SetSymbolDirPath(FPaths::ConvertRelativePathToFull(dirPath)); + PlatformSettings->SaveConfig(); + BuildGeneralSettingsBox(GeneralSettingsBox); + } + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnClearSymbolDirPath() +{ + if (PlatformSettings != NULL) + { + PlatformSettings->SetSymbolDirPath(""); + PlatformSettings->SaveConfig(); + BuildGeneralSettingsBox(GeneralSettingsBox); + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnSelect2DLaunchPath() +{ + + if (PlatformSettings != NULL) + { + TSharedPtr<SWindow> parentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + const void* parentWindowHandle = (parentWindow.IsValid() && parentWindow->GetNativeWindow().IsValid()) ? parentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr; + TArray<FString> path; + FString defaultPath = PlatformSettings->OculusRift2DLaunchPath.IsEmpty() ? FPaths::ProjectContentDir() : PlatformSettings->OculusRift2DLaunchPath; + if (FDesktopPlatformModule::Get()->OpenFileDialog(parentWindowHandle, "Choose 2D Launch File", defaultPath, defaultPath, "Executables (*.exe)|*.exe", EFileDialogFlags::None, path)) + { + if (path.Num() > 0) + { + PlatformSettings->OculusRift2DLaunchPath = FPaths::ConvertRelativePathToFull(path[0]); + } + PlatformSettings->SaveConfig(); + BuildRiftOptionalFields(OptionalSettings); + } + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnClear2DLaunchPath() +{ + if (PlatformSettings != NULL) + { + PlatformSettings->OculusRift2DLaunchPath.Empty(); + PlatformSettings->SaveConfig(); + BuildRiftOptionalFields(OptionalSettings); + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnCancelUpload() +{ + if (FMessageDialog::Open(EAppMsgType::OkCancel, LOCTEXT("CancelUploadWarning", "Are you sure you want to cancel the upload process?")) == EAppReturnType::Ok) + { + if (PlatformProcess.IsValid()) + { + FPlatformProcess::TerminateProc(PlatformProcess); + UpdateLogText(LogText + LOCTEXT("UploadCancel", "Upload process was canceled.").ToString()); + } + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnSelectLanguagePacksPath() +{ + + if (PlatformSettings != NULL) + { + TSharedPtr<SWindow> parentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + const void* parentWindowHandle = (parentWindow.IsValid() && parentWindow->GetNativeWindow().IsValid()) ? parentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr; + FString path; + FString defaultPath = PlatformSettings->GetLanguagePacksPath().IsEmpty() ? FPaths::ProjectContentDir() : PlatformSettings->GetLanguagePacksPath(); + if (FDesktopPlatformModule::Get()->OpenDirectoryDialog(parentWindowHandle, "Choose Language Packs Directory", defaultPath, path)) + { + PlatformSettings->SetLanguagePacksPath(path); + PlatformSettings->SaveConfig(); + BuildExpansionFileBox(ExpansionFilesSettings); + } + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnClearLanguagePacksPath() +{ + if (PlatformSettings != NULL) + { + PlatformSettings->SetLanguagePacksPath(""); + PlatformSettings->SaveConfig(); + BuildExpansionFileBox(ExpansionFilesSettings); + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnSelectExpansionFilesPath() +{ + + if (PlatformSettings != NULL) + { + TSharedPtr<SWindow> parentWindow = FSlateApplication::Get().FindWidgetWindow(AsShared()); + const void* parentWindowHandle = (parentWindow.IsValid() && parentWindow->GetNativeWindow().IsValid()) ? parentWindow->GetNativeWindow()->GetOSWindowHandle() : nullptr; + FString path; + FString defaultPath = PlatformSettings->GetExpansionFilesPath().IsEmpty() ? FPaths::ProjectContentDir() : PlatformSettings->GetExpansionFilesPath(); + if (FDesktopPlatformModule::Get()->OpenDirectoryDialog(parentWindowHandle, "Choose Expansion Files Directory", defaultPath, path)) + { + if (!path.Equals(PlatformSettings->GetExpansionFilesPath())) + { + if (!path.IsEmpty() && FPaths::DirectoryExists(path)) + { + TArray<FString> Files; + //FFileManagerGeneric::Get().FindFilesRecursive(Files, *path, TEXT("*.*"), true, false, false); + IFileManager::Get().FindFiles(Files, *path); + + TArray<FOculusXRAssetConfig>* AssetConfigs = PlatformSettings->GetAssetConfigs(); + for (int i = 0; i < Files.Num(); i++) + { + FOculusXRAssetConfig AssetConfig; + AssetConfig.Name = Files[i]; + AssetConfigs->Push(AssetConfig); + } + + PlatformSettings->SetExpansionFilesPath(path); + PlatformSettings->SaveConfig(); + BuildExpansionFileBox(ExpansionFilesSettings); + } + } + } + } + return FReply::Handled(); +} + +FReply SOculusPlatformToolWidget::OnClearExpansionFilesPath() +{ + if (PlatformSettings != NULL) + { + PlatformSettings->SetExpansionFilesPath(""); + PlatformSettings->GetAssetConfigs()->Empty(); + PlatformSettings->SaveConfig(); + BuildExpansionFileBox(ExpansionFilesSettings); + } + return FReply::Handled(); +} + +void SOculusPlatformToolWidget::ValidateTextField(PFieldValidatorDel del, FString text, FString name, bool& success) +{ + FString error = ""; + FFieldValidatorDel fieldValidator; + + // Check the given field with the given field validator and print the error if it fails. + fieldValidator.BindSP(this, del); + if (!fieldValidator.Execute(text, error)) + { + FString errorMessage = LOCTEXT("Error", "ERROR: Please verify that the {0} is correct. ").ToString(); + errorMessage = FString::Format(*errorMessage, { name }); + UpdateLogText(LogText + errorMessage + (error.IsEmpty() ? "\n" : error + "\n")); + success = false; + } +} + +bool SOculusPlatformToolWidget::GenericFieldValidator(FString text, FString& error) +{ + if (text.IsEmpty()) + { + error = LOCTEXT("FieldEmpty", "The field is empty.").ToString(); + return false; + } + return true; +} + +bool SOculusPlatformToolWidget::IDFieldValidator(FString text, FString& error) +{ + const FRegexPattern RegExPat(TEXT("^[0-9]+$")); + FRegexMatcher RegMatcher(RegExPat, text); + + if (!GenericFieldValidator(text, error)) + { + return false; + } + else if (!RegMatcher.FindNext()) + { + error = LOCTEXT("InvalidChar", "The field contains invalid characters.").ToString(); + return false; + } + return true; +} + +bool SOculusPlatformToolWidget::DirectoryFieldValidator(FString text, FString& error) +{ + if (!GenericFieldValidator(text, error)) + { + return false; + } + if (!FPaths::DirectoryExists(text)) + { + error = LOCTEXT("DirectoryNull", "The directory does not exist.").ToString(); + return false; + } + return true; +} + +bool SOculusPlatformToolWidget::FileFieldValidator(FString text, FString& error) +{ + if (!GenericFieldValidator(text, error)) + { + return false; + } + if (!FPaths::FileExists(text)) + { + error = LOCTEXT("FileNull", "The file does not exist.").ToString(); + return false; + } + return true; +} + +bool SOculusPlatformToolWidget::LaunchParamValidator(FString text, FString& error) +{ + if (text.Contains("\"")) + { + error = LOCTEXT("LaunchParamError", "The field contains illegal characters.").ToString(); + return false; + } + return true; +} + +void SOculusPlatformToolWidget::On2DOptionsExpanded(bool bExpanded) +{ + Options2DCollapsed = !bExpanded; +} + +void SOculusPlatformToolWidget::OnRedistPackagesExpanded(bool bExpanded) +{ + OptionsRedistPackagesCollapsed = !bExpanded; +} + +void SOculusPlatformToolWidget::UpdateLogText(FString text) +{ + // Make sure that log text updating happens on the right thread. + LogText = text; + LogTextUpdated = true; +} + +void SOculusPlatformToolWidget::SetPlatformProcess(FProcHandle proc) +{ + PlatformProcess = proc; +} + +//======================================================================================= +//FPlatformDownloadTask + +FPlatformDownloadTask::FPlatformDownloadTask(FUpdateLogTextDel textDel, FEvent* saveEvent) +{ + UpdateLogText = textDel; + SaveCompleteEvent = saveEvent; + + FOculusXRHMDModule::GetPluginWrapper().SendEvent2("oculus_platform_tool", "provision_util", "integration"); +} + +void FPlatformDownloadTask::DoWork() +{ + // Create HTTP request for downloading oculus platform tool + downloadCompleteEvent = FGenericPlatformProcess::GetSynchEventFromPool(false); + TSharedRef<IHttpRequest, ESPMode::ThreadSafe> httpRequest = FHttpModule::Get().CreateRequest(); + + httpRequest->OnProcessRequestComplete().BindRaw(this, &FPlatformDownloadTask::OnDownloadRequestComplete); + httpRequest->OnRequestProgress().BindRaw(this, &FPlatformDownloadTask::OnRequestDownloadProgress); + httpRequest->SetURL(UrlPlatformUtil); + httpRequest->SetVerb("GET"); + + httpRequest->ProcessRequest(); + + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("DownloadProgress", "Downloading Platform Tool: {0}%\n").ToString()); + ToolConsoleLog = SOculusPlatformToolWidget::LogText; + UpdateProgressLog(0); + + // Wait for download to complete + downloadCompleteEvent->Wait(); + + // Save HTTP data + FString fullPath = FPaths::ConvertRelativePathToFull(FPaths::ProjectContentDir()) + ProjectPlatformUtilPath; + if (FFileHelper::SaveArrayToFile(httpData, *fullPath)) + { + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("DownloadSuccess", "Platform tool successfully downloaded.\n").ToString()); + } + else + { + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("DownloadError", "An error has occured with downloading the platform tool.\n").ToString()); + } + + if (SaveCompleteEvent != NULL) + { + SaveCompleteEvent->Trigger(); + } +} + +void FPlatformDownloadTask::UpdateProgressLog(int progress) +{ + UpdateLogText.Execute(FString::Format(*ToolConsoleLog, { progress })); +} + +void FPlatformDownloadTask::OnRequestDownloadProgress(FHttpRequestPtr HttpRequest, int32 BytesSend, int32 InBytesReceived) +{ + // Update progress on download in tool console log + FHttpResponsePtr httpResponse = HttpRequest->GetResponse(); + if (httpResponse.IsValid()) + { + int progress = ((float)InBytesReceived / (float)httpResponse->GetContentLength()) * 100; + UpdateProgressLog(progress); + } +} + +void FPlatformDownloadTask::OnDownloadRequestComplete(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded) +{ + // Extract data from HTTP response and trigger download complete event + if (bSucceeded && HttpResponse.IsValid()) + { + httpData = HttpResponse->GetContent(); + downloadCompleteEvent->Trigger(); + } +} + +//======================================================================================= +//FPlatformUploadTask + +FPlatformUploadTask::FPlatformUploadTask(FString args, FEnableUploadButtonDel del, FUpdateLogTextDel textDel, FSetProcessDel procDel) +{ + LaunchArgs = args; + EnableUploadButton = del; + UpdateLogText = textDel; + SetProcess = procDel; + + EnableUploadButton.Execute(false); +} + +void FPlatformUploadTask::DoWork() +{ + // Check if the platform tool exists in the project directory. If not, start process to download it. + if (!FPaths::FileExists(FPaths::ProjectContentDir() + ProjectPlatformUtilPath)) + { + FEvent* PlatformToolCreatedEvent = FGenericPlatformProcess::GetSynchEventFromPool(false); + + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("NoCLI", "Unable to find Oculus Platform Utility.\n").ToString()); + EAppReturnType::Type dialogChoice = FMessageDialog::Open(EAppMsgType::OkCancel, OculusPlatformDialogMessage, &OculusPlatformDialogTitle); + if (dialogChoice == EAppReturnType::Ok) + { + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("DownloadCLI", "Downloading Oculus Platform Utility . . .\n").ToString()); + (new FAsyncTask<FPlatformDownloadTask>(UpdateLogText, PlatformToolCreatedEvent))->StartBackgroundTask(); + PlatformToolCreatedEvent->Wait(); + } + else + { + return; + } + + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("StartUploadAfterDownload", "Starting upload . . .\n").ToString()); + } + + // Start up the CLI and pass in arguments. + FPlatformProcess::CreatePipe(ReadPipe, WritePipe); + FProcHandle PlatformProcess = FPlatformProcess::CreateProc(*(FPaths::ProjectContentDir() + ProjectPlatformUtilPath), *LaunchArgs, false, true, true, nullptr, 0, nullptr, WritePipe, ReadPipe); + SetProcess.Execute(PlatformProcess); + + // Redirect CLI output to the tool's log. + while (FPlatformProcess::IsProcRunning(PlatformProcess)) + { + FString log = FPlatformProcess::ReadPipe(ReadPipe); + if (!log.IsEmpty()) + { + // Remove parts of the log that contain escape character codes + int32 escapeIndex = log.Find("\u001b"); + while (escapeIndex >= 0) + { + int32 lineEndIndex = log.Find("\n", ESearchCase::IgnoreCase, ESearchDir::FromStart, escapeIndex); + if (lineEndIndex < 0) // If an escape character code exists without a new line end, just remove the escape character + { + lineEndIndex = escapeIndex + 1; + } + log.RemoveAt(escapeIndex, lineEndIndex - escapeIndex); + escapeIndex = log.Find("\u001b"); + } + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + log); + } + } + EnableUploadButton.Execute(true); +} + +//======================================================================================= +//FPlatformLoadRedistPackagesTask + +FPlatformLoadRedistPackagesTask::FPlatformLoadRedistPackagesTask(FUpdateLogTextDel textDel) +{ + UpdateLogText = textDel; +} + +void FPlatformLoadRedistPackagesTask::DoWork() +{ + UOculusXRPlatformToolSettings* PlatformSettings = GetMutableDefault<UOculusXRPlatformToolSettings>(); + + // Check to see if the CLI exists, we need this to load avalible redist packages + if (!FPaths::FileExists(FPaths::ProjectContentDir() + ProjectPlatformUtilPath)) + { + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("LoadRedist", "Loading redistributable packages . . .\n").ToString()); + + FEvent* PlatformToolCreatedEvent = FGenericPlatformProcess::GetSynchEventFromPool(false); + + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("NoCLI", "Unable to find Oculus Platform Utility.\n").ToString()); + EAppReturnType::Type dialogChoice = FMessageDialog::Open(EAppMsgType::OkCancel, OculusPlatformDialogMessage, &OculusPlatformDialogTitle); + if (dialogChoice == EAppReturnType::Ok) + { + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("DownloadCLI", "Downloading Oculus Platform Utility . . .\n").ToString()); + (new FAsyncTask<FPlatformDownloadTask>(UpdateLogText, PlatformToolCreatedEvent))->StartBackgroundTask(); + PlatformToolCreatedEvent->Wait(); + } + else + { + return; + } + } + + // Launch CLI and pass command to list out redist packages currently avalible + TArray<FOculusXRRedistPackage> LoadedPackages; + FString Args = "list-redists"; + FPlatformProcess::CreatePipe(ReadPipe, WritePipe); + FProcHandle PlatformProcess = FPlatformProcess::CreateProc(*(FPaths::ProjectContentDir() + ProjectPlatformUtilPath), *Args, false, true, true, nullptr, 0, nullptr, WritePipe, ReadPipe); + + // Load redist packages + while (FPlatformProcess::IsProcRunning(PlatformProcess)) + { + FString log = FPlatformProcess::ReadPipe(ReadPipe); + if (!log.IsEmpty() && !log.Contains("\u001b") && !log.Contains("ID")) + { + TArray<FString> Packages; + log.ParseIntoArrayLines(Packages); + if (Packages.Num() > 0) + { + for (int i = 0; i < Packages.Num(); i++) + { + FString id, name; + Packages[i].Split("|", &id, &name); + + if (!id.IsEmpty() && !name.IsEmpty()) + { + FOculusXRRedistPackage newPackage; + newPackage.Name = name; + newPackage.Id = id; + + LoadedPackages.Add(newPackage); + } + } + } + } + } + + // Check to see if our stored copy of redist packages is outdated + if (PlatformSettings != NULL) + { + if (LoadedPackages.Num() > PlatformSettings->OculusRedistPackages.Num()) + { + PlatformSettings->OculusRedistPackages = LoadedPackages; + PlatformSettings->SaveConfig(); + UpdateLogText.Execute(SOculusPlatformToolWidget::LogText + LOCTEXT("FinishRedistLoad", "Finished updating redistributable packages.\n").ToString()); + } + } +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolWidget.h b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..a868ff96e861d17a7cf02c24fa28c8c964e9094b --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRPlatformToolWidget.h @@ -0,0 +1,221 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "CoreMinimal.h" +#include "OculusXRPlatformToolSettings.h" +#include "Widgets/SWidget.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SMultiLineEditableTextBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/STextComboBox.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Input/SCheckBox.h" +#include "Engine/PostProcessVolume.h" +#include "Framework/Text/SlateHyperlinkRun.h" +#include "HttpModule.h" +#include "HttpManager.h" +#include "Interfaces/IHttpResponse.h" +#include "Async/AsyncWork.h" +#include "HAL/Event.h" +#include "HAL/ThreadSafeBool.h" +#include "OculusXRPluginWrapper.h" +#include "Brushes/SlateDynamicImageBrush.h" + +class SOculusPlatformToolWidget; + +// Function Delegates +DECLARE_DELEGATE_OneParam(FEnableUploadButtonDel, bool); +DECLARE_DELEGATE_OneParam(FUpdateLogTextDel, FString); +DECLARE_DELEGATE_OneParam(FSetProcessDel, FProcHandle); +DECLARE_DELEGATE_RetVal_TwoParams(bool, FFieldValidatorDel, FString, FString&); + +class SOculusPlatformToolWidget : public SCompoundWidget +{ +public: + typedef void(SOculusPlatformToolWidget::*PTextComboBoxDel)(TSharedPtr<FString>, ESelectInfo::Type); + typedef void(SOculusPlatformToolWidget::*PTextComittedDel)(const FText&, ETextCommit::Type); + typedef FReply(SOculusPlatformToolWidget::*PButtonClickedDel)(); + typedef bool(SOculusPlatformToolWidget::*PFieldValidatorDel)(FString, FString&); + typedef void(SOculusPlatformToolWidget::*PCheckBoxChangedDel)(ECheckBoxState); + + SLATE_BEGIN_ARGS(SOculusPlatformToolWidget) + {} + SLATE_END_ARGS(); + + SOculusPlatformToolWidget(); + void Construct(const FArguments& InArgs); + virtual void Tick(const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime) override; + + static FString LogText; + +private: + TSharedPtr<SMultiLineEditableTextBox> ToolConsoleLog; + TSharedPtr<SVerticalBox> GeneralSettingsBox; + TSharedPtr<SHorizontalBox> ButtonToolbar; + TSharedPtr<SVerticalBox> OptionalSettings; + TSharedPtr<SVerticalBox> ExpansionFilesSettings; + TSharedPtr<FSlateDynamicImageBrush> ODHIconDynamicImageBrush; + + UEnum* PlatformEnum; + UEnum* GamepadEmulationEnum; + UEnum* AssetTypeEnum; + UOculusXRPlatformToolSettings* PlatformSettings; + TArray<TSharedPtr<FString>> OculusPlatforms; + TArray<TSharedPtr<FString>> RiftGamepadEmulation; + TArray<TSharedPtr<FString>> AssetType; + + bool Options2DCollapsed; + bool OptionsRedistPackagesCollapsed; + bool ActiveUploadButton; + bool RequestUploadButtonActive; + FProcHandle PlatformProcess; + FThreadSafeBool LogTextUpdated; + + FEnableUploadButtonDel EnableUploadButtonDel; + FUpdateLogTextDel UpdateLogTextDel; + FSetProcessDel SetProcessDel; + + // Callbacks + FReply OnStartPlatformUpload(); + FReply OnSelectRiftBuildDirectory(); + FReply OnClearRiftBuildDirectory(); + FReply OnSelectLaunchFilePath(); + FReply OnClearLaunchFilePath(); + FReply OnSelectSymbolDirPath(); + FReply OnClearSymbolDirPath(); + FReply OnSelect2DLaunchPath(); + FReply OnClear2DLaunchPath(); + FReply OnCancelUpload(); + FReply OnSelectLanguagePacksPath(); + FReply OnClearLanguagePacksPath(); + FReply OnSelectExpansionFilesPath(); + FReply OnClearExpansionFilesPath(); + + FString GenerateSymbolPath(); + + void OnPlatformSettingChanged(TSharedPtr<FString> ItemSelected, ESelectInfo::Type SelectInfo); + void OnApplicationIDChanged(const FText& InText, ETextCommit::Type InCommitType); + void OnApplicationTokenChanged(const FText& InText, ETextCommit::Type InCommitType); + void OnReleaseChannelChanged(const FText& InText, ETextCommit::Type InCommitType); + void OnReleaseNoteChanged(const FText& InText, ETextCommit::Type InCommitType); + void OnRiftBuildVersionChanged(const FText& InText, ETextCommit::Type InCommitType); + void OnRiftLaunchParamsChanged(const FText& InText, ETextCommit::Type InCommitType); + void OnRiftFirewallChanged(ECheckBoxState CheckState); + void OnRedistPackageStateChanged(ECheckBoxState CheckState, FOculusXRRedistPackage* Package); + void OnRiftGamepadEmulationChanged(TSharedPtr<FString> ItemSelected, ESelectInfo::Type SelectInfo); + void On2DLaunchParamsChanged(const FText& InText, ETextCommit::Type InCommitType); + void On2DOptionsExpanded(bool bExpanded); + void OnRedistPackagesExpanded(bool bExpanded); + void OnAssetConfigRequiredChanged(ECheckBoxState CheckState, int i); + void OnAssetConfigTypeChanged(TSharedPtr<FString> ItemSelected, ESelectInfo::Type SelectInfo, int i); + void OnAssetConfigSKUChanged(const FText& InText, ETextCommit::Type InCommitType, int i); + void OnUploadDebugSymbolsChanged(ECheckBoxState CheckState); + void OnDebugSymbolsOnlyChanged(ECheckBoxState CheckState); + void OnBuildIDChanged(const FText& InText, ETextCommit::Type InCommitType); + + // UI Constructors + void BuildGeneralSettingsBox(TSharedPtr<SVerticalBox> box); + void BuildTextComboBoxField(TSharedPtr<SVerticalBox> box, FText name, TArray<TSharedPtr<FString>>* options, TSharedPtr<FString> current, PTextComboBoxDel deleg, int32 indentAmount = 0); + void BuildTextField(TSharedPtr<SVerticalBox> box, FText name, FText text, FText tooltip, PTextComittedDel deleg, bool isPassword = false, int32 indentAmount = 0); + void BuildFileDirectoryField(TSharedPtr<SVerticalBox> box, FText name, FText path, FText tooltip, PButtonClickedDel deleg, PButtonClickedDel clearDeleg, int32 indentAmount = 0); + void BuildCheckBoxField(TSharedPtr<SVerticalBox> box, FText name, bool check, FText tooltip, PCheckBoxChangedDel deleg, int32 indentAmount = 0); + void BuildButtonToolbar(TSharedPtr<SHorizontalBox> box); + void BuildRiftOptionalFields(TSharedPtr<SVerticalBox> area); + void BuildRedistPackagesBox(TSharedPtr<SVerticalBox> box); + void BuildExpansionFileBox(TSharedPtr<SVerticalBox> box); + void BuildAssetConfigBox(TSharedPtr<SVerticalBox> box, FOculusXRAssetConfig config, int index); + + // Text Field Validators + void ValidateTextField(PFieldValidatorDel del, FString text, FString name, bool& success); + bool GenericFieldValidator(FString text, FString& error); + bool IDFieldValidator(FString text, FString& error); + bool DirectoryFieldValidator(FString text, FString& error); + bool FileFieldValidator(FString text, FString& error); + bool LaunchParamValidator(FString text, FString& error); + + bool ConstructArguments(FString& args); + bool ConstructDebugSymbolArguments(FString& args); + void EnableUploadButton(bool enabled); + void LoadConfigSettings(); + void UpdateLogText(FString text); + void SetPlatformProcess(FProcHandle proc); + void LoadRedistPackages(); +}; + +class FPlatformDownloadTask : public FNonAbandonableTask +{ + friend class FAsyncTask<FPlatformDownloadTask>; + +private: + FUpdateLogTextDel UpdateLogText; + FString ToolConsoleLog; + FEvent* downloadCompleteEvent; + FEvent* SaveCompleteEvent; + TArray<uint8> httpData; + +public: + FPlatformDownloadTask(FUpdateLogTextDel textDel, FEvent* saveEvent); + + void OnDownloadRequestComplete(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded); + void OnRequestDownloadProgress(FHttpRequestPtr HttpRequest, int32 BytesSend, int32 InBytesReceived); + +protected: + void DoWork(); + void UpdateProgressLog(int progress); + + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(FPlatformDownloadTask, STATGROUP_ThreadPoolAsyncTasks); + } +}; + +class FPlatformUploadTask : public FNonAbandonableTask +{ + friend class FAsyncTask<FPlatformUploadTask>; + +public: + FPlatformUploadTask(FString args, FEnableUploadButtonDel del, FUpdateLogTextDel textDel, FSetProcessDel procDel); + +private: + void* ReadPipe; + void* WritePipe; + + FSetProcessDel SetProcess; + FUpdateLogTextDel UpdateLogText; + FEnableUploadButtonDel EnableUploadButton; + FString LaunchArgs; + +protected: + void DoWork(); + + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(FPlatformUploadTask, STATGROUP_ThreadPoolAsyncTasks); + } +}; + +class FPlatformLoadRedistPackagesTask : public FNonAbandonableTask +{ + friend class FAsyncTask<FPlatformLoadRedistPackagesTask>; + +public: + FPlatformLoadRedistPackagesTask(FUpdateLogTextDel textDel); + +private: + void* ReadPipe; + void* WritePipe; + + FUpdateLogTextDel UpdateLogText; + +protected: + void DoWork(); + + FORCEINLINE TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(FPlatformLoadRedistPackagesTask, STATGROUP_ThreadPoolAsyncTasks); + } +}; + diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolCommands.cpp b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolCommands.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fe3c84664803b4bc614264fb8a296c0622f07d88 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolCommands.cpp @@ -0,0 +1,19 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRToolCommands.h" +#include "Framework/Docking/TabManager.h" + +#define LOCTEXT_NAMESPACE "FOculusXREditorModule" + +void FOculusToolCommands::RegisterCommands() +{ + UI_COMMAND(OpenPluginWindow, "Oculus Tool", "Show Oculus Tool Window", EUserInterfaceActionType::Button, FInputChord()); + UI_COMMAND(ToggleDeploySo, "Deploy compiled .so directly to device", "Faster deploy when we only have code changes by deploying compiled .so directly to device", EUserInterfaceActionType::ToggleButton, FInputChord()); +} + +void FOculusToolCommands::ShowOculusTool() +{ + FGlobalTabmanager::Get()->TryInvokeTab(FOculusXREditorModule::OculusPerfTabName); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolCommands.h b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolCommands.h new file mode 100644 index 0000000000000000000000000000000000000000..dbdfd6a193569d2bcfcfb0013a678e402903f4d1 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolCommands.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Framework/Commands/Commands.h" +#include "OculusXRToolStyle.h" +#include "OculusXREditorModule.h" +#include "HAL/IConsoleManager.h" + + class FOculusToolCommands : public TCommands<FOculusToolCommands> + { + public: + + FOculusToolCommands() + : TCommands<FOculusToolCommands>(TEXT("OculusTool"), NSLOCTEXT("Contexts", "OculusXREditor", "OculusXREditor Plugin"), NAME_None, FOculusToolStyle::GetStyleSetName()), + ShowOculusToolCommand(TEXT("vr.oculus.ShowToolWindow"), + *NSLOCTEXT("OculusRift", "CCommandText_ShowToolWindow", "Show the Oculus Editor Tool window (editor only).").ToString(), + FConsoleCommandDelegate::CreateRaw(this, &FOculusToolCommands::ShowOculusTool) + ) + { + } + + // TCommands<> interface + virtual void RegisterCommands() override; + + public: + TSharedPtr< FUICommandInfo > OpenPluginWindow; + TSharedPtr< FUICommandInfo > ToggleDeploySo; + + private: + void ShowOculusTool(); + + private: + FAutoConsoleCommand ShowOculusToolCommand; + }; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolStyle.cpp b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolStyle.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9dedc0907177a65341f7a11081cd81bd8409c5f0 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolStyle.cpp @@ -0,0 +1,71 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRToolStyle.h" +#include "Styling/SlateStyleRegistry.h" +#include "Framework/Application/SlateApplication.h" +#include "Slate/SlateGameResources.h" +#include "Interfaces/IPluginManager.h" + +TSharedPtr< FSlateStyleSet > FOculusToolStyle::StyleInstance = NULL; + +void FOculusToolStyle::Initialize() +{ + if (!StyleInstance.IsValid()) + { + StyleInstance = Create(); + FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance); + } +} + +void FOculusToolStyle::Shutdown() +{ + FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance); + ensure(StyleInstance.IsUnique()); + StyleInstance.Reset(); +} + +FName FOculusToolStyle::GetStyleSetName() +{ + static FName StyleSetName(TEXT("OculusToolStyle")); + return StyleSetName; +} + +#define IMAGE_BRUSH( RelativePath, ... ) FSlateImageBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BOX_BRUSH( RelativePath, ... ) FSlateBoxBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define BORDER_BRUSH( RelativePath, ... ) FSlateBorderBrush( Style->RootToContentDir( RelativePath, TEXT(".png") ), __VA_ARGS__ ) +#define TTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".ttf") ), __VA_ARGS__ ) +#define OTF_FONT( RelativePath, ... ) FSlateFontInfo( Style->RootToContentDir( RelativePath, TEXT(".otf") ), __VA_ARGS__ ) + +const FVector2D Icon16x16(16.0f, 16.0f); +const FVector2D Icon20x20(20.0f, 20.0f); +const FVector2D Icon40x40(40.0f, 40.0f); + +TSharedRef< FSlateStyleSet > FOculusToolStyle::Create() +{ + TSharedRef< FSlateStyleSet > Style = MakeShareable(new FSlateStyleSet("OculusToolStyle")); + Style->SetContentRoot(IPluginManager::Get().FindPlugin("OculusXR")->GetBaseDir() / TEXT("Resources")); + + Style->Set("OculusTool.MenuButton", new IMAGE_BRUSH(TEXT("ButtonIcon_80x"), Icon40x40)); + Style->Set("OculusTool.OpenPluginWindow", new IMAGE_BRUSH(TEXT("ButtonIcon_80x"), Icon40x40)); + + return Style; +} + +#undef IMAGE_BRUSH +#undef BOX_BRUSH +#undef BORDER_BRUSH +#undef TTF_FONT +#undef OTF_FONT + +void FOculusToolStyle::ReloadTextures() +{ + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().GetRenderer()->ReloadTextureResources(); + } +} + +const ISlateStyle& FOculusToolStyle::Get() +{ + return *StyleInstance; +} diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolStyle.h b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolStyle.h new file mode 100644 index 0000000000000000000000000000000000000000..8b4c8f0343fc34561788ab5c3b6696d01d04eb3d --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolStyle.h @@ -0,0 +1,32 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Styling/SlateStyle.h" + +/** */ +class FOculusToolStyle +{ +public: + + static void Initialize(); + + static void Shutdown(); + + /** reloads textures used by slate renderer */ + static void ReloadTextures(); + + /** @return The Slate style set for the Shooter game */ + static const ISlateStyle& Get(); + + static FName GetStyleSetName(); + +private: + + static TSharedRef< class FSlateStyleSet > Create(); + +private: + + static TSharedPtr< class FSlateStyleSet > StyleInstance; +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolWidget.cpp b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolWidget.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8cb52981852be65972a8a62e545a524db51ab568 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolWidget.cpp @@ -0,0 +1,1024 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRToolWidget.h" +#include "OculusXREditorSettings.h" +#include "OculusXRHMDRuntimeSettings.h" +#include "OculusXRHMD.h" +#include "DetailLayoutBuilder.h" +#include "Engine/RendererSettings.h" +#include "Engine/Blueprint.h" +#include "GeneralProjectSettings.h" +#include "AndroidRuntimeSettings.h" +#include "EngineUtils.h" +#include "Editor.h" +#include "Styling/AppStyle.h" +#include "Widgets/Text/SRichTextBlock.h" +#include "UObject/EnumProperty.h" +#include "EdGraph/EdGraph.h" +#include "Widgets/Input/SCheckBox.h" +#include "UnrealEdMisc.h" + +#define CALL_MEMBER_FUNCTION(object, memberFn) ((object).*(memberFn)) + +#define LOCTEXT_NAMESPACE "OculusToolWidget" + +#define MIN_SDK_VERSION 29 + +// Misc notes and known issues: +// * I save after every change because UE4 wasn't prompting to save on exit, but this makes it tough for users to undo, and doesn't prompt shader rebuild. Alternatives? + +TSharedRef<SHorizontalBox> SOculusToolWidget::CreateSimpleSetting(SimpleSetting* setting) +{ + auto box = SNew(SHorizontalBox).Visibility(this, &SOculusToolWidget::IsVisible, setting->tag) + + SHorizontalBox::Slot().FillWidth(10).VAlign(VAlign_Center) + [ + SNew(SRichTextBlock) + .Visibility(this, &SOculusToolWidget::IsVisible, setting->tag) + .DecoratorStyleSet(&FAppStyle::Get()) + .Text(setting->description).AutoWrapText(true) + + SRichTextBlock::HyperlinkDecorator(TEXT("HyperlinkDecorator"), this, &SOculusToolWidget::OnBrowserLinkClicked) + ]; + + for (int i = 0; i < setting->actions.Num(); ++i) + { + box.Get().AddSlot() + .AutoWidth().VAlign(VAlign_Top) + [ + SNew(SButton) + .Text(setting->actions[i].buttonText) + .OnClicked(this, setting->actions[i].ClickFunc, true) + .Visibility(this, &SOculusToolWidget::IsVisible, setting->tag) + ]; + } + + box.Get().AddSlot().AutoWidth().VAlign(VAlign_Top) + [ + SNew(SButton) + .Text(LOCTEXT("IgnorePerfRec", "Ignore")) + .OnClicked(this, &SOculusToolWidget::IgnoreRecommendation, setting->tag) + .Visibility(this, &SOculusToolWidget::IsVisible, setting->tag) + ]; + return box; +} + +EVisibility SOculusToolWidget::IsVisible(FName tag) const +{ + const SimpleSetting* setting = SimpleSettings.Find(tag); + checkf(setting != NULL, TEXT("Failed to find tag %s."), *tag.ToString()); + if(SettingIgnored(setting->tag)) return EVisibility::Collapsed; + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + EOculusXRPlatform targetPlatform = EditorSettings->PerfToolTargetPlatform; + + if(targetPlatform == EOculusXRPlatform::Mobile && !((int)setting->supportMask & (int)SupportFlags::SupportMobile)) return EVisibility::Collapsed; + if(targetPlatform == EOculusXRPlatform::PC && !((int)setting->supportMask & (int)SupportFlags::SupportPC)) return EVisibility::Collapsed; + + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + const bool bForwardShading = UsingForwardShading(); + if (bForwardShading && ((int)setting->supportMask & (int)SupportFlags::ExcludeForward)) return EVisibility::Collapsed; + if (!bForwardShading && ((int)setting->supportMask & (int)SupportFlags::ExcludeDeferred)) return EVisibility::Collapsed; + + return CALL_MEMBER_FUNCTION(*this, setting->VisFunc)(setting->tag); +} + +void SOculusToolWidget::AddSimpleSetting(TSharedRef<SVerticalBox> box, SimpleSetting* setting) +{ + box.Get().AddSlot().AutoHeight() + .Padding(5, 5) + [ + CreateSimpleSetting(setting) + ]; +} + +bool SOculusToolWidget::SettingIgnored(FName settingKey) const +{ + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + bool* ignoreSetting = EditorSettings->PerfToolIgnoreList.Find(settingKey); + return (ignoreSetting != NULL && *ignoreSetting == true); +} + +TSharedRef<SVerticalBox> SOculusToolWidget::NewCategory(TSharedRef<SScrollBox> scroller, FText heading) +{ + scroller.Get().AddSlot() + .Padding(0, 0) + [ + SNew(SBorder) + .BorderImage( FAppStyle::GetBrush("ToolPanel.DarkGroupBorder") ) + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot().Padding(5,5).FillWidth(1) + [ + SNew(SRichTextBlock) + .TextStyle(FAppStyle::Get(), "ToolBar.Heading") + .DecoratorStyleSet(&FAppStyle::Get()).AutoWrapText(true) + .Text(heading) + + SRichTextBlock::HyperlinkDecorator(TEXT("HyperlinkDecorator"), this, &SOculusToolWidget::OnBrowserLinkClicked) + ] + ] + ]; + + TSharedPtr<SVerticalBox> box; + scroller.Get().AddSlot() + .Padding(0, 0, 0, 2) + [ + SNew(SBorder) + .BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder")) + [ + SAssignNew(box, SVerticalBox) + ] + ]; + return box.ToSharedRef(); +} + +void SOculusToolWidget::RebuildLayout() +{ + if (!ScrollingContainer.IsValid()) return; + TSharedRef<SScrollBox> scroller = ScrollingContainer.ToSharedRef(); + + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + uint8 initiallySelected = 0; + for (uint8 i = 0; i < (uint8)EOculusXRPlatform::Length; ++i) + { + if ((uint8)EditorSettings->PerfToolTargetPlatform == i) + { + initiallySelected = i; + } + } + + scroller.Get().ClearChildren(); + + scroller.Get().AddSlot() + .Padding(2, 2) + [ + SNew(SVerticalBox) + +SVerticalBox::Slot().AutoHeight() + [ + SNew(SBorder) + //.BorderImage( FAppStyle::GetBrush("ToolPanel.LightGroupBorder") ).Visibility(this, &SOculusToolWidget::RestartVisible) + .BorderImage( FAppStyle::GetBrush("SceneOutliner.ChangedItemHighlight") ).Visibility(this, &SOculusToolWidget::RestartVisible) + .Padding(2) + [ + SNew(SBorder) + .BorderImage( FAppStyle::GetBrush("ToolPanel.DarkGroupBorder") ) + .Padding(2) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(10).VAlign(VAlign_Center) + [ + SNew(SRichTextBlock) + .Text(LOCTEXT("RestartRequired", "<RichTextBlock.TextHighlight>Restart required:You have made changes that require an editor restart to take effect.</>")).DecoratorStyleSet(&FAppStyle::Get()) + ] + + SHorizontalBox::Slot().AutoWidth().VAlign(VAlign_Top) + [ + SNew(SButton) + .Text(LOCTEXT("RestartNow", "Restart Editor")) + .OnClicked(this, &SOculusToolWidget::OnRestartClicked) + ] + ] + ] + ] + ]; + + TSharedRef<SVerticalBox> box = NewCategory(scroller, LOCTEXT("GeneralSettings", "<RichTextBlock.Bold>General Settings</>")); + + box.Get().AddSlot() + .Padding(5, 5) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(10).VAlign(VAlign_Top) + [ + SNew(SRichTextBlock) + .Text(LOCTEXT("TargetPlatform", "Target Platform: (This setting changes which recommendations are displayed, but does NOT modify your project.)")) + ] + +SHorizontalBox::Slot().FillWidth(1).VAlign(VAlign_Top) + [ + SNew(STextComboBox) + .OptionsSource( &Platforms ) + .InitiallySelectedItem(Platforms[initiallySelected]) + .OnSelectionChanged( this, &SOculusToolWidget::OnChangePlatform ) + ] + ]; + /* + // Omitting this option for now, because the tool is currently something you only need to launch once or twice. + // If later tabs end up increasing use cases significantly we may re-add. + box.Get().AddSlot() + .Padding(5, 5) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(10).VAlign(VAlign_Top) + [ + SNew(SRichTextBlock) + .Text(LOCTEXT("ShowToolButtonInEditor", "Add Oculus Tool Button to editor (change appears after restart in Windows -> Developer Tools -> Oculus Tool):")) + ] + +SHorizontalBox::Slot().FillWidth(1).VAlign(VAlign_Top) + [ + SNew(SCheckBox) + .OnCheckStateChanged( this, &SOculusToolWidget::OnShowButtonChanged ) + .IsChecked( this, &SOculusToolWidget::IsShowButtonChecked ) + ] + ]; + */ + + AddSimpleSetting(box, SimpleSettings.Find(FName("StartInVR"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("SupportDash"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("ForwardShading"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("AllowStaticLighting"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("MultiView"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("MobileMultiView"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("MobileMSAA"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("MobilePostProcessing"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("MobileVulkan"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("AndroidManifest"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("AndroidPackaging"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("AndroidQuestArch"))); + + box = NewCategory(scroller, LOCTEXT("PostProcessHeader", "<RichTextBlock.Bold>Post-Processing Settings:</>\nThe below settings all refer to your project's post-processing settings. Post-processing can be very expensive in VR, so we recommend disabling many expensive post-processing effects. You can fine-tune your post-processing settings with a Post Process Volume. <a href=\"https://docs.unrealengine.com/SharingAndReleasing/XRDevelopment/VR/VRPerformanceAndProfiling\" id=\"HyperlinkDecorator\">Read more.</>.")); + AddSimpleSetting(box, SimpleSettings.Find(FName("LensFlare"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("AntiAliasing"))); + + DynamicLights.Empty(); + + for (TObjectIterator<ULightComponentBase> LightItr; LightItr; ++LightItr) + { + AActor* owner = LightItr->GetOwner(); + if (owner != NULL && (owner->IsRootComponentStationary() || owner->IsRootComponentMovable()) && !owner->IsHiddenEd() && LightItr->IsVisible() && owner->IsEditable() && owner->IsSelectable() && LightItr->GetWorld() == GEditor->GetEditorWorldContext().World()) + { + // GetFullGroupName() must be used as the key as GetName() is not unique + FString lightIgnoreKey = "IgnoreLight_" + LightItr->GetFullGroupName(false); + if (!SettingIgnored(FName(lightIgnoreKey.GetCharArray().GetData()))) + { + DynamicLights.Add(LightItr->GetFullGroupName(false), TWeakObjectPtr<ULightComponentBase>(*LightItr)); + } + } + } + + if (DynamicLights.Num() > 0) + { + box = NewCategory(scroller, LOCTEXT("DynamicLightsHeader", "<RichTextBlock.Bold>Dynamic Lights:</>\nThe following lights are not static. They will use dynamic lighting instead of lightmaps, and will be much more expensive on the GPU. (Most of the cost will show up in the GPU profiler as ShadowDepths and ShadowProjectonOnOpaque.) In some cases they will also give superior results. This is a fidelity-performance tradeoff. <a href=\"https://docs.unrealengine.com/en-us/Engine/Rendering/LightingAndShadows/LightMobility\" id=\"HyperlinkDecorator\">Read more.</>\nFixes: select the light and change its mobility to stationary to pre-compute its lighting. You will need to rebuild lightmaps. Alternatively, you can disable Cast Shadows.")); + + for (auto it = DynamicLights.CreateIterator(); it; ++it) + { + box.Get().AddSlot() + .Padding(5, 5) + .AutoHeight() + [ + SNew(SHorizontalBox) + +SHorizontalBox::Slot().FillWidth(5).VAlign(VAlign_Center) + [ + SNew(STextBlock) + .Text(FText::FromString(it->Key)) + ] + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("SelectLight", "Select Light")) + .OnClicked(this, &SOculusToolWidget::SelectLight, it->Key) + ] + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("IgnoreLight", "Ignore Light")) + .OnClicked(this, &SOculusToolWidget::IgnoreLight, it->Key) + ] + ]; + } + } + + box = NewCategory(scroller, LOCTEXT("ShaderPermutationHeader", "<RichTextBlock.Bold>Shader Permutation Reduction:</>\nThe below settings all refer to your project's shader permutation settings.")); + AddSimpleSetting(box, SimpleSettings.Find(FName("MobileShaderStaticAndCSMShadowReceivers"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("MobileShaderAllowDistanceFieldShadows"))); + AddSimpleSetting(box, SimpleSettings.Find(FName("MobileShaderAllowMovableDirectionalLights"))); + + box = NewCategory(scroller, FText::GetEmpty()); + box.Get().AddSlot() + .Padding(10, 5) + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(10) + [ + SNew(STextBlock) + .Text(LOCTEXT("UnhidePerfIgnores", "Unhide all ignored recommendations.")).AutoWrapText(true) + .Visibility(this, &SOculusToolWidget::CanUnhideIgnoredRecommendations) + ] + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("UnhidePerfIgnoresButton", "Unhide")) + .OnClicked(this, &SOculusToolWidget::UnhideIgnoredRecommendations) + .Visibility(this, &SOculusToolWidget::CanUnhideIgnoredRecommendations) + ] + ]; + box.Get().AddSlot() + .Padding(10, 5).AutoHeight() + [ + SNew(SHorizontalBox) + + SHorizontalBox::Slot().FillWidth(10) + + SHorizontalBox::Slot().AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("RefreshButton", "Refresh")) + .OnClicked(this, &SOculusToolWidget::UnhideIgnoredRecommendations) + ] + ]; +} + +void SOculusToolWidget::Construct(const FArguments& InArgs) +{ + pendingRestart = false; + PlatformEnum = StaticEnum<EOculusXRPlatform>(); + Platforms.Reset(2); + + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + for (uint8 i = 0; i < (uint8)EOculusXRPlatform::Length; ++i) + { + Platforms.Add(MakeShareable(new FString(PlatformEnum->GetDisplayNameTextByIndex((int64)i).ToString()))); + } + + PostProcessVolume = NULL; + for (TActorIterator<APostProcessVolume> ActorItr(GEditor->GetEditorWorldContext().World()); ActorItr; ++ActorItr) + { + PostProcessVolume = *ActorItr; + } + + SimpleSettings.Add(FName("StartInVR"), { + FName("StartInVR"), + LOCTEXT("StartInVRDescription", "Enable the \"Start in VR\" setting to ensure your app starts in VR. (You can also ignore this and pass -vr at the command line.)"), + &SOculusToolWidget::StartInVRVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportPC + }); + SimpleSettings.Find(FName("StartInVR"))->actions.Add( + { LOCTEXT("StartInVRButtonText", "Enable Start in VR"), + &SOculusToolWidget::StartInVREnable } + ); + + SimpleSettings.Add(FName("SupportDash"), { + FName("SupportDash"), + LOCTEXT("SupportDashDescription", "Dash support is not enabled. Click to enable it, but make sure to handle the appropriate focus events. <a href=\"https://developer.oculus.com/documentation/unreal/latest/concepts/unreal-dash/\" id=\"HyperlinkDecorator\">Read more.</>"), + &SOculusToolWidget::SupportDashVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportPC + }); + SimpleSettings.Find(FName("SupportDash"))->actions.Add( + { LOCTEXT("SupportDashButtonText", "Enable Dash Support"), + &SOculusToolWidget::SupportDashEnable } + ); + + SimpleSettings.Add(FName("ForwardShading"), { + FName("ForwardShading"), + LOCTEXT("ForwardShadingDescription", "Forward shading is not enabled for this project. Forward shading is often better suited for VR rendering. <a href=\"https://docs.unrealengine.com/en-us/Engine/Performance/ForwardRenderer\" id=\"HyperlinkDecorator\">Read more.</>"), + &SOculusToolWidget::ForwardShadingVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportPC // | (int)SupportFlags::SupportMobile // not including mobile because mobile is forced to use forward regardless of this setting + }); + SimpleSettings.Find(FName("ForwardShading"))->actions.Add( + { LOCTEXT("ForwardShadingButtonText", "Enable Forward Shading"), + &SOculusToolWidget::ForwardShadingEnable } + ); + + SimpleSettings.Add(FName("MultiView"), { + FName("MultiView"), + LOCTEXT("InstancedStereoDescription", "Instanced stereo is not enabled for this project. Instanced stereo substantially reduces draw calls, and improves rendering performance."), + &SOculusToolWidget::MultiViewVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportPC + }); + SimpleSettings.Find(FName("MultiView"))->actions.Add( + { LOCTEXT("InstancedStereoButtonText", "Enable Instanced Stereo"), + &SOculusToolWidget::MultiViewEnable } + ); + + SimpleSettings.Add(FName("MobileMultiView"), { + FName("MobileMultiView"), + LOCTEXT("MobileMultiViewDescription", "Enable mobile multi-view and direct mobile multi-view to significantly reduce CPU overhead."), + &SOculusToolWidget::MobileMultiViewVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("MobileMultiView"))->actions.Add( + { LOCTEXT("MobileMultiViewButton", "Enable Multi-View"), + &SOculusToolWidget::MobileMultiViewEnable } + ); + + SimpleSettings.Add(FName("MobileMSAA"), { + FName("MobileMSAA"), + LOCTEXT("MobileMSAADescription", "Enable Mobile MSAA 4x to get higher quality antialiasing at a reasonable cost on mobile codepaths."), + &SOculusToolWidget::MobileMSAAVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("MobileMSAA"))->actions.Add( + { LOCTEXT("MobileMSAAButton", "Enable MSAA 4x"), + &SOculusToolWidget::MobileMSAAEnable } + ); + + SimpleSettings.Add(FName("MobilePostProcessing"), { + FName("MobilePostProcessing"), + LOCTEXT("MobileHDRDescription", "Mobile HDR has performance and stability issues in VR. We strongly recommend disabling it."), + &SOculusToolWidget::MobilePostProcessingVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("MobilePostProcessing"))->actions.Add( + { LOCTEXT("MobileHDRButton", "Disable Mobile HDR"), + &SOculusToolWidget::MobilePostProcessingDisable } + ); + + SimpleSettings.Add(FName("MobileVulkan"), { + FName("MobileVulkan"), + LOCTEXT("MobileVulkanDescription", "Oculus recommends using Vulkan as the rendering backend for all mobile apps."), + &SOculusToolWidget::MobileVulkanVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("MobileVulkan"))->actions.Add( + { LOCTEXT("MobileVulkanButton", "Use Vulkan Rendering Backend"), + &SOculusToolWidget::MobileVulkanEnable } + ); + + SimpleSettings.Add(FName("AndroidManifest"), { + FName("AndroidManifest"), + LOCTEXT("AndroidManifestDescription", "You need to select a target device in \"Package for Oculus Mobile device\" for all mobile apps. <a href=\"https://developer.oculus.com/documentation/unreal/latest/concepts/unreal-quick-start-guide-go/\" id=\"HyperlinkDecorator\">Read more.</>"), + &SOculusToolWidget::AndroidManifestVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("AndroidManifest"))->actions.Add( + { LOCTEXT("AndroidManifestButtonQuest", "Select Oculus Quest"), + &SOculusToolWidget::AndroidManifestQuest } + ); + + SimpleSettings.Add(FName("AndroidPackaging"), { + FName("AndroidPackaging"), + LOCTEXT("AndroidPackagingDescription", "Some mobile packaging settings need to be fixed. (SDK versions, and FullScreen Immersive settings.) <a href=\"https://developer.oculus.com/documentation/unreal/latest/concepts/unreal-quick-start-guide-go/\" id=\"HyperlinkDecorator\">Read more.</>"), + &SOculusToolWidget::AndroidPackagingVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("AndroidPackaging"))->actions.Add( + { LOCTEXT("AndroidPackagingButton", "Configure Android Packaging"), + &SOculusToolWidget::AndroidPackagingFix } + ); + + SimpleSettings.Add(FName("AndroidQuestArch"), { + FName("AndroidQuestArch"), + LOCTEXT("AndroidQuestArchDescription", "Oculus Quest store requires 64-bit applications. <a href=\"https://developer.oculus.com/blog/quest-submission-policy-update-64-bit-by-default/\" id=\"HyperlinkDecorator\">Read more.</>"), + &SOculusToolWidget::AndroidQuestArchVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("AndroidQuestArch"))->actions.Add( + { LOCTEXT("AndroidQuestArchButton", "Enable Android Arm64 CPU architecture support"), + &SOculusToolWidget::AndroidQuestArchFix } + ); + + // Post-Processing Settings + SimpleSettings.Add(FName("LensFlare"), { + FName("LensFlare"), + LOCTEXT("LensFlareDescription", "Lens flare is enabled. It can be expensive, and exhibit visible artifacts in VR."), + &SOculusToolWidget::LensFlareVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile | (int)SupportFlags::SupportPC + }); + SimpleSettings.Find(FName("LensFlare"))->actions.Add( + { LOCTEXT("LensFlareButton", "Disable Lens Flare"), + &SOculusToolWidget::LensFlareDisable } + ); + + // Only used for PC right now. Mobile MSAA is a separate setting. + SimpleSettings.Add(FName("AntiAliasing"), { + FName("AntiAliasing"), + LOCTEXT("AntiAliasingDescription", "The forward render supports MSAA and Temporal anti-aliasing. Enable one of these for the best VR visual-performance tradeoff. (This button will enable temporal anti-aliasing. You can enable MSAA instead in Edit -> Project Settings -> Rendering.)"), + &SOculusToolWidget::AntiAliasingVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportPC | (int)SupportFlags::ExcludeDeferred + }); + SimpleSettings.Find(FName("AntiAliasing"))->actions.Add( + { LOCTEXT("AntiAliasingButton", "Enable Temporal AA"), + &SOculusToolWidget::AntiAliasingEnable } + ); + + SimpleSettings.Add(FName("AllowStaticLighting"), { + FName("AllowStaticLighting"), + LOCTEXT("AllowStaticLightingDescription", "Your project does not allow static lighting. You should only disallow static lighting if you intend for your project to be 100% dynamically lit."), + &SOculusToolWidget::AllowStaticLightingVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile | (int)SupportFlags::SupportPC + }); + SimpleSettings.Find(FName("AllowStaticLighting"))->actions.Add( + { LOCTEXT("AllowStaticLightingButton", "Allow Static Lighting"), + &SOculusToolWidget::AllowStaticLightingEnable } + ); + + // Mobile Shader Permutation Reduction + SimpleSettings.Add(FName("MobileShaderStaticAndCSMShadowReceivers"), { + FName("MobileShaderStaticAndCSMShadowReceivers"), + LOCTEXT("MobileShaderStaticAndCSMShadowReceiversDescription", "Your project does not contain any stationary lights. Support Combined Static and CSM Shadowing can be disabled to reduce shader permutations."), + &SOculusToolWidget::MobileShaderStaticAndCSMShadowReceiversVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("MobileShaderStaticAndCSMShadowReceivers"))->actions.Add( + { LOCTEXT("MobileShaderStaticAndCSMShadowReceiversButton", "Disable Support Combined Static and CSM Shadowing"), + &SOculusToolWidget::MobileShaderStaticAndCSMShadowReceiversDisable } + ); + + SimpleSettings.Add(FName("MobileShaderAllowDistanceFieldShadows"), { + FName("MobileShaderAllowDistanceFieldShadows"), + LOCTEXT("MobileShaderAllowDistanceFieldShadowsDescription", "Your project does not contain any stationary lights. Support Support Distance Field Shadows can be disabled to reduce shader permutations."), + &SOculusToolWidget::MobileShaderAllowDistanceFieldShadowsVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("MobileShaderAllowDistanceFieldShadows"))->actions.Add( + { LOCTEXT("MobileShaderAllowDistanceFieldShadowsButton", "Disable Support Support Distance Field Shadows"), + &SOculusToolWidget::MobileShaderAllowDistanceFieldShadowsDisable } + ); + + SimpleSettings.Add(FName("MobileShaderAllowMovableDirectionalLights"), { + FName("MobileShaderAllowMovableDirectionalLights"), + LOCTEXT("MobileShaderAllowMovableDirectionalLightsDescription", "Your project does not contain any movable lights. Support Movable Directional Lights can be disabled to reduce shader permutations."), + & SOculusToolWidget::MobileShaderAllowMovableDirectionalLightsVisibility, + TArray<SimpleSettingAction>(), + (int)SupportFlags::SupportMobile + }); + SimpleSettings.Find(FName("MobileShaderAllowMovableDirectionalLights"))->actions.Add( + { LOCTEXT("MobileShaderAllowMovableDirectionalLightsButton", "Disable Support Movable Directional Lights"), + &SOculusToolWidget::MobileShaderAllowMovableDirectionalLightsDisable } + ); + + auto scroller = SNew(SScrollBox); + ScrollingContainer = scroller; + RebuildLayout(); + + ChildSlot + [ + SNew(SBorder) + .BorderImage( FAppStyle::GetBrush("ToolPanel.LightGroupBorder") ) + .Padding(2) + [ + scroller + ] + ]; +} + +void SOculusToolWidget::OnBrowserLinkClicked(const FSlateHyperlinkRun::FMetadata& Metadata) +{ + const FString* url = Metadata.Find(TEXT("href")); + + if ( url != NULL ) + { + FPlatformProcess::LaunchURL(**url, NULL, NULL); + } +} + +FReply SOculusToolWidget::OnRestartClicked() +{ + FUnrealEdMisc::Get().RestartEditor(true); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::RestartVisible() const +{ + return pendingRestart ? EVisibility::Visible : EVisibility::Collapsed; +} + +void SOculusToolWidget::OnChangePlatform(TSharedPtr<FString> ItemSelected, ESelectInfo::Type SelectInfo) +{ + if (!ItemSelected.IsValid()) + { + return; + } + + int32 idx = PlatformEnum->GetIndexByNameString(*ItemSelected); + if (idx != INDEX_NONE) + { + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + EditorSettings->PerfToolTargetPlatform = (EOculusXRPlatform)idx; + EditorSettings->SaveConfig(); + } + RebuildLayout(); +} + +FReply SOculusToolWidget::IgnoreRecommendation(FName tag) +{ + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + EditorSettings->PerfToolIgnoreList.Add(tag, true); + EditorSettings->SaveConfig(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::CanUnhideIgnoredRecommendations() const +{ + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + return EditorSettings->PerfToolIgnoreList.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed; +} + +FReply SOculusToolWidget::UnhideIgnoredRecommendations() +{ + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + EditorSettings->PerfToolIgnoreList.Empty(); + EditorSettings->SaveConfig(); + RebuildLayout(); + return FReply::Handled(); +} + +bool SOculusToolWidget::UsingForwardShading() const +{ + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + EOculusXRPlatform targetPlatform = EditorSettings->PerfToolTargetPlatform; + return targetPlatform == EOculusXRPlatform::Mobile || Settings->bForwardShading; + +} + +FReply SOculusToolWidget::Refresh() +{ + RebuildLayout(); + return FReply::Handled(); +} + +void SOculusToolWidget::SuggestRestart() +{ + pendingRestart = true; +} + +FReply SOculusToolWidget::ForwardShadingEnable(bool text) +{ + IConsoleVariable* CVar = IConsoleManager::Get().FindConsoleVariable(ANSI_TO_TCHAR("r.ForwardShading")); + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bForwardShading = 1; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bForwardShading)), Settings->GetDefaultConfigFilename()); + SuggestRestart(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::ForwardShadingVisibility(FName tag) const +{ + return UsingForwardShading() ? EVisibility::Collapsed : EVisibility::Visible; +} + +FReply SOculusToolWidget::MultiViewEnable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bMultiView = 1; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bMultiView)), Settings->GetDefaultConfigFilename()); + SuggestRestart(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::MultiViewVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + const bool bMultiView = Settings->bMultiView != 0; + + return bMultiView ? EVisibility::Collapsed : EVisibility::Visible; +} + +FReply SOculusToolWidget::MobileMultiViewEnable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bMobileMultiView = 1; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bMobileMultiView)), Settings->GetDefaultConfigFilename()); + SuggestRestart(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::MobileMultiViewVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + const bool bMMV = Settings->bMobileMultiView != 0; + + return bMMV ? EVisibility::Collapsed : EVisibility::Visible; +} + +FReply SOculusToolWidget::MobileMSAAEnable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->MobileAntiAliasing = EMobileAntiAliasingMethod::MSAA; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, MobileAntiAliasing)), Settings->GetDefaultConfigFilename()); + Settings->MSAASampleCount = ECompositingSampleCount::Four; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, MSAASampleCount)), Settings->GetDefaultConfigFilename()); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::MobileMSAAVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + const bool bMobileMSAAValid = Settings->MobileAntiAliasing == EMobileAntiAliasingMethod::MSAA && Settings->MSAASampleCount == ECompositingSampleCount::Four; + + return bMobileMSAAValid ? EVisibility::Collapsed : EVisibility::Visible; +} + +FReply SOculusToolWidget::MobileVulkanEnable(bool text) +{ + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + Settings->bSupportsVulkan = true; + Settings->bBuildForES31 = false; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bSupportsVulkan)), Settings->GetDefaultConfigFilename()); + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bBuildForES31)), Settings->GetDefaultConfigFilename()); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::MobileVulkanVisibility(FName tag) const +{ + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + return Settings->bSupportsVulkan && !Settings->bBuildForES31 ? EVisibility::Collapsed : EVisibility::Visible; +} + +FReply SOculusToolWidget::MobilePostProcessingDisable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bMobilePostProcessing = 0; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bMobilePostProcessing)), Settings->GetDefaultConfigFilename()); + SuggestRestart(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::MobilePostProcessingVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + return Settings->bMobilePostProcessing == 0 ? EVisibility::Collapsed : EVisibility::Visible; +} + +FString SOculusToolWidget::GetConfigPath() const +{ + return FString::Printf(TEXT("%sDefaultEngine.ini"), *FPaths::SourceConfigDir()); +} + +FReply SOculusToolWidget::AndroidManifestQuest(bool text) +{ + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + Settings->PackageForOculusMobile.Add(EOculusMobileDevice::Quest); + Settings->PackageForOculusMobile.Add(EOculusMobileDevice::Quest2); +#ifdef WITH_OCULUS_BRANCH + Settings->PackageForOculusMobile.Add(EOculusMobileDevice::QuestPro); +#endif // WITH_OCULUS_BRANCH + Settings->SaveConfig(CPF_Config, *Settings->GetDefaultConfigFilename()); // UpdateSinglePropertyInConfigFile does not support arrays + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::AndroidManifestVisibility(FName tag) const +{ + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + return Settings->PackageForOculusMobile.Num() <= 0 ? EVisibility::Visible : EVisibility::Collapsed; +} + +FReply SOculusToolWidget::AndroidPackagingFix(bool text) +{ + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + Settings->bFullScreen = true; + Settings->MinSDKVersion = MIN_SDK_VERSION; + Settings->TargetSDKVersion = MIN_SDK_VERSION; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bFullScreen)), Settings->GetDefaultConfigFilename()); + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, MinSDKVersion)), Settings->GetDefaultConfigFilename()); + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, TargetSDKVersion)), Settings->GetDefaultConfigFilename()); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::AndroidPackagingVisibility(FName tag) const +{ + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + return ( + Settings->MinSDKVersion != MIN_SDK_VERSION || + Settings->TargetSDKVersion != MIN_SDK_VERSION || + !Settings->bFullScreen + ) ? EVisibility::Visible : EVisibility::Collapsed; +} + +FReply SOculusToolWidget::AndroidQuestArchFix(bool text) +{ + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + Settings->bBuildForArm64 = true; + Settings->bBuildForX8664 = false; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bBuildForArm64)), Settings->GetDefaultConfigFilename()); + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UAndroidRuntimeSettings, bBuildForX8664)), Settings->GetDefaultConfigFilename()); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::AndroidQuestArchVisibility(FName tag) const +{ + UAndroidRuntimeSettings* Settings = GetMutableDefault<UAndroidRuntimeSettings>(); + return (Settings->PackageForOculusMobile.Num() > 0) && !Settings->bBuildForArm64 ? + EVisibility::Visible : EVisibility::Collapsed; +} + +FReply SOculusToolWidget::AntiAliasingEnable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->DefaultFeatureAntiAliasing = EAntiAliasingMethod::AAM_TemporalAA; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, DefaultFeatureAntiAliasing)), Settings->GetDefaultConfigFilename()); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::AntiAliasingVisibility(FName tag) const +{ + // TODO: can we get MSAA level? 2 is fast, 4 is reasonable, anything higher is insane. + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + + static IConsoleVariable* CVarMSAACount = IConsoleManager::Get().FindConsoleVariable(TEXT("r.MSAACount")); + CVarMSAACount->Set(4); + + const bool bAADisabled = UsingForwardShading() && Settings->DefaultFeatureAntiAliasing != EAntiAliasingMethod::AAM_TemporalAA && Settings->DefaultFeatureAntiAliasing != EAntiAliasingMethod::AAM_MSAA; + + return bAADisabled ? EVisibility::Visible : EVisibility::Collapsed; +} + +FReply SOculusToolWidget::AllowStaticLightingEnable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bAllowStaticLighting = true; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bAllowStaticLighting)), Settings->GetDefaultConfigFilename()); + SuggestRestart(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::AllowStaticLightingVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + return Settings->bAllowStaticLighting ? EVisibility::Collapsed : EVisibility::Visible; +} + +FReply SOculusToolWidget::MobileShaderStaticAndCSMShadowReceiversDisable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bMobileEnableStaticAndCSMShadowReceivers = false; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bMobileEnableStaticAndCSMShadowReceivers)), Settings->GetDefaultConfigFilename()); + SuggestRestart(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::MobileShaderStaticAndCSMShadowReceiversVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + if (!Settings->bMobileEnableStaticAndCSMShadowReceivers) + { + return EVisibility::Collapsed; + } + + for (const auto& kvp : DynamicLights) + { + AActor* owner = kvp.Value->GetOwner(); + if (owner != NULL && owner->IsRootComponentStationary()) + { + return EVisibility::Collapsed; + } + } + + return EVisibility::Visible; +} + +FReply SOculusToolWidget::MobileShaderAllowDistanceFieldShadowsDisable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bMobileAllowDistanceFieldShadows = false; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bMobileAllowDistanceFieldShadows)), Settings->GetDefaultConfigFilename()); + SuggestRestart(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::MobileShaderAllowDistanceFieldShadowsVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + if (!Settings->bMobileAllowDistanceFieldShadows) + { + return EVisibility::Collapsed; + } + + for (const auto& kvp : DynamicLights) + { + AActor* owner = kvp.Value->GetOwner(); + if (owner != NULL && owner->IsRootComponentStationary()) + { + return EVisibility::Collapsed; + } + } + + return EVisibility::Visible; +} + +FReply SOculusToolWidget::MobileShaderAllowMovableDirectionalLightsDisable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bMobileAllowMovableDirectionalLights = false; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bMobileAllowMovableDirectionalLights)), Settings->GetDefaultConfigFilename()); + SuggestRestart(); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::MobileShaderAllowMovableDirectionalLightsVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + if (!Settings->bMobileAllowMovableDirectionalLights) + { + return EVisibility::Collapsed; + } + + for (const auto& kvp : DynamicLights) + { + AActor* owner = kvp.Value->GetOwner(); + if (owner != NULL && owner->IsRootComponentMovable()) + { + return EVisibility::Collapsed; + } + } + + return EVisibility::Visible; +} + +void SOculusToolWidget::OnShowButtonChanged(ECheckBoxState NewState) +{ + GConfig->SetBool(TEXT("/Script/OculusXREditor.OculusXREditorSettings"), TEXT("bAddMenuOption"), NewState == ECheckBoxState::Checked ? true : false, FString::Printf(TEXT("%sDefaultEditor.ini"), *FPaths::SourceConfigDir())); + GConfig->Flush(0); +} + +ECheckBoxState SOculusToolWidget::IsShowButtonChecked() const +{ + bool v; + GConfig->GetBool(TEXT("/Script/OculusXREditor.OculusXREditorSettings"), TEXT("bAddMenuOption"), v, FString::Printf(TEXT("%sDefaultEditor.ini"), *FPaths::SourceConfigDir())); + return v ? ECheckBoxState::Checked : ECheckBoxState::Unchecked; +} + +FReply SOculusToolWidget::LensFlareDisable(bool text) +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + Settings->bDefaultFeatureLensFlare = false; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(URendererSettings, bDefaultFeatureLensFlare)), Settings->GetDefaultConfigFilename()); + + if (PostProcessVolume != NULL) + { + PostProcessVolume->Settings.bOverride_LensFlareIntensity = 0; + Settings->SaveConfig(); + } + + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::LensFlareVisibility(FName tag) const +{ + URendererSettings* Settings = GetMutableDefault<URendererSettings>(); + bool bLensFlare = Settings->bDefaultFeatureLensFlare != 0; + + if (PostProcessVolume != NULL) + { + if (PostProcessVolume->Settings.bOverride_LensFlareIntensity != 0) + { + bLensFlare = PostProcessVolume->Settings.LensFlareIntensity > 0.0f; + } + } + + return bLensFlare ? EVisibility::Visible : EVisibility::Collapsed; +} + +FReply SOculusToolWidget::SelectLight(FString lightName) +{ + const TWeakObjectPtr< ULightComponentBase>* weakPtr = DynamicLights.Find(lightName); + if (weakPtr) + { + ULightComponentBase* light = weakPtr->Get(); + GEditor->SelectNone(true, true); + GEditor->SelectActor(light->GetOwner(), true, true); + GEditor->SelectComponent(light, true, true, true); + } + return FReply::Handled(); +} + +FReply SOculusToolWidget::IgnoreLight(FString lightName) +{ + UOculusXREditorSettings* EditorSettings = GetMutableDefault<UOculusXREditorSettings>(); + FString lightIgnoreKey = "IgnoreLight_" + lightName; + EditorSettings->PerfToolIgnoreList.Add(FName(lightIgnoreKey.GetCharArray().GetData()), true); + EditorSettings->SaveConfig(); + return FReply::Handled(); +} + +FReply SOculusToolWidget::StartInVREnable(bool text) +{ + UGeneralProjectSettings* Settings = GetMutableDefault<UGeneralProjectSettings>(); + Settings->bStartInVR = 1; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UGeneralProjectSettings, bStartInVR)), Settings->GetDefaultConfigFilename()); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::StartInVRVisibility(FName tag) const +{ + const UGeneralProjectSettings* Settings = GetDefault<UGeneralProjectSettings>(); + const bool bStartInVR = Settings->bStartInVR != 0; + return bStartInVR ? EVisibility::Collapsed : EVisibility::Visible; + return EVisibility::Collapsed; +} + +FReply SOculusToolWidget::SupportDashEnable(bool text) +{ + UOculusXRHMDRuntimeSettings* Settings = GetMutableDefault<UOculusXRHMDRuntimeSettings>(); + Settings->bSupportsDash = true; + Settings->UpdateSinglePropertyInConfigFile(Settings->GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bSupportsDash)), Settings->GetDefaultConfigFilename()); + return FReply::Handled(); +} + +EVisibility SOculusToolWidget::SupportDashVisibility(FName tag) const +{ + const UOculusXRHMDRuntimeSettings* Settings = GetDefault<UOculusXRHMDRuntimeSettings>(); + return Settings->bSupportsDash ? EVisibility::Collapsed : EVisibility::Visible; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolWidget.h b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolWidget.h new file mode 100644 index 0000000000000000000000000000000000000000..20b15d5c6081a73836cf846d1e5518cbd53c434e --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Private/OculusXRToolWidget.h @@ -0,0 +1,145 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/SWidget.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SComboBox.h" +#include "Widgets/Input/STextComboBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Layout/SScrollBox.h" +#include "Engine/PostProcessVolume.h" +#include "Framework/Text/SlateHyperlinkRun.h" +#include "Components/LightComponentBase.h" + +class SOculusToolWidget; + +enum class SupportFlags : int +{ + None = 0x00, + SupportPC = 0x01, + SupportMobile = 0x02, + ExcludeForward = 0x04, + ExcludeDeferred = 0x08 +}; + +typedef struct _SimpleSettingAction +{ + FText buttonText; + FReply(SOculusToolWidget::*ClickFunc)(bool); +} SimpleSettingAction; + +typedef struct _SimpleSetting +{ + FName tag; + FText description; + EVisibility(SOculusToolWidget::*VisFunc)(FName) const; + TArray<SimpleSettingAction> actions; + int supportMask; // bitfield of SupportFlags +} SimpleSetting; + +/** Widget allowing the user to create new gameplay tags */ +class SOculusToolWidget : public SCompoundWidget +{ +public: + + SLATE_BEGIN_ARGS(SOculusToolWidget) + {} + SLATE_END_ARGS(); + + void Construct(const FArguments& InArgs); + +protected: + void RebuildLayout(); + void SuggestRestart(); + + void AddSimpleSetting(TSharedRef<SVerticalBox> scroller, SimpleSetting* setting); + TSharedRef<SHorizontalBox> CreateSimpleSetting(SimpleSetting* setting); + TSharedRef<SVerticalBox> NewCategory(TSharedRef<SScrollBox> scroller, FText heading); + + void OnBrowserLinkClicked(const FSlateHyperlinkRun::FMetadata& Metadata); + FReply OnRestartClicked(); + EVisibility RestartVisible() const; + FReply IgnoreRecommendation(FName tag); + FReply UnhideIgnoredRecommendations(); + bool UsingForwardShading() const; + FString GetConfigPath() const; + FReply Refresh(); + EVisibility CanUnhideIgnoredRecommendations() const; + EVisibility IsVisible(FName tag) const; + bool SettingIgnored(FName settingKey) const; + + void OnChangePlatform(TSharedPtr<FString> ItemSelected, ESelectInfo::Type SelectInfo); + + FReply ForwardShadingEnable(bool text); + EVisibility ForwardShadingVisibility(FName tag) const; + + FReply MultiViewEnable(bool text); + EVisibility MultiViewVisibility(FName tag) const; + + FReply StartInVREnable(bool text); + EVisibility StartInVRVisibility(FName tag) const; + + FReply SupportDashEnable(bool text); + EVisibility SupportDashVisibility(FName tag) const; + + FReply LensFlareDisable(bool text); + EVisibility LensFlareVisibility(FName tag) const; + + FReply MobileMultiViewEnable(bool text); + EVisibility MobileMultiViewVisibility(FName tag) const; + + FReply MobileMSAAEnable(bool text); + EVisibility MobileMSAAVisibility(FName tag) const; + + FReply MobilePostProcessingDisable(bool text); + EVisibility MobilePostProcessingVisibility(FName tag) const; + + FReply MobileVulkanEnable(bool text); + EVisibility MobileVulkanVisibility(FName tag) const; + + FReply AndroidManifestGo(bool text); + FReply AndroidManifestQuest(bool text); + EVisibility AndroidManifestVisibility(FName tag) const; + + FReply AndroidPackagingFix(bool text); + EVisibility AndroidPackagingVisibility(FName tag) const; + + FReply AndroidQuestArchFix(bool text); + EVisibility AndroidQuestArchVisibility(FName tag) const; + + FReply AntiAliasingEnable(bool text); + EVisibility AntiAliasingVisibility(FName tag) const; + + FReply AllowStaticLightingEnable(bool text); + EVisibility AllowStaticLightingVisibility(FName tag) const; + + FReply MobileShaderStaticAndCSMShadowReceiversDisable(bool text); + EVisibility MobileShaderStaticAndCSMShadowReceiversVisibility(FName tag) const; + + FReply MobileShaderAllowDistanceFieldShadowsDisable(bool text); + EVisibility MobileShaderAllowDistanceFieldShadowsVisibility(FName tag) const; + + FReply MobileShaderAllowMovableDirectionalLightsDisable(bool text); + EVisibility MobileShaderAllowMovableDirectionalLightsVisibility(FName tag) const; + + FReply SelectLight(FString lightName); + FReply IgnoreLight(FString lightName); + + void OnShowButtonChanged( ECheckBoxState NewState ); + ECheckBoxState IsShowButtonChecked() const; + + APostProcessVolume* PostProcessVolume; + UEnum* PlatformEnum; + TArray<TSharedPtr<FString>> Platforms; + TMap<FName, SimpleSetting> SimpleSettings; + + TMap<FString, TWeakObjectPtr<ULightComponentBase> > DynamicLights; + + TSharedPtr<SScrollBox> ScrollingContainer; + + bool pendingRestart; +}; diff --git a/Plugins/OculusXR/Source/OculusXREditor/Public/IOculusXREditorModule.h b/Plugins/OculusXR/Source/OculusXREditor/Public/IOculusXREditorModule.h new file mode 100644 index 0000000000000000000000000000000000000000..8c5595538904b787d85a5f4eb2b702e4c3a6900c --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Public/IOculusXREditorModule.h @@ -0,0 +1,19 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" + +class FToolBarBuilder; +class FMenuBuilder; + +#define OCULUS_EDITOR_MODULE_NAME "OculusXREditor" + +////////////////////////////////////////////////////////////////////////// +// IOculusXREditorModule + +class IOculusXREditorModule : public IModuleInterface +{ +}; + diff --git a/Plugins/OculusXR/Source/OculusXREditor/Public/OculusXREditorSettings.h b/Plugins/OculusXR/Source/OculusXREditor/Public/OculusXREditorSettings.h new file mode 100644 index 0000000000000000000000000000000000000000..a1ab9acaf7cd7aff2cfb06585a648e6d1d095ff5 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Public/OculusXREditorSettings.h @@ -0,0 +1,36 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "OculusXREditorSettings.generated.h" + +UENUM() +enum class EOculusXRPlatform : uint8 +{ + PC UMETA(DisplayName="PC"), + Mobile UMETA(DisplayName="Mobile"), + Length UMETA(DisplayName="Invalid") +}; + +/** + * + */ +UCLASS(config=Editor) +class OCULUSXREDITOR_API UOculusXREditorSettings : public UObject +{ + GENERATED_BODY() + +public: + UOculusXREditorSettings(); + + UPROPERTY(config, EditAnywhere, Category = MetaXR ) + TMap<FName, bool> PerfToolIgnoreList; + + UPROPERTY(config, EditAnywhere, Category = MetaXR ) + EOculusXRPlatform PerfToolTargetPlatform; + + UPROPERTY(globalconfig, EditAnywhere, Category = MetaXR ) + bool bAddMenuOption; +}; diff --git a/Plugins/OculusXR/Source/OculusXREditor/Public/OculusXRPlatformToolSettings.h b/Plugins/OculusXR/Source/OculusXREditor/Public/OculusXRPlatformToolSettings.h new file mode 100644 index 0000000000000000000000000000000000000000..798fda1ddaa6d638f298983f5754a2c6f0ab2442 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREditor/Public/OculusXRPlatformToolSettings.h @@ -0,0 +1,271 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/NoExportTypes.h" +#include "OculusXRPlatformToolSettings.generated.h" + +UENUM() +enum class EOculusXRPlatformTarget : uint8 +{ + Rift UMETA(DisplayName="Rift"), + Quest UMETA(DisplayName="Quest"), + Length UMETA(DisplayName="Invalid") +}; + +UENUM() +enum class EOculusXRGamepadEmulation : uint8 +{ + Off UMETA(DisplayName="Off"), + Twinstick UMETA(DisplayName = "Twinstick"), + RightDPad UMETA(DisplayName = "Right D Pad"), + LeftDPad UMETA(DisplayName = "Left D Pad"), + Length UMETA(DisplayName = "Invalid") +}; + +UENUM() +enum class EOculusXRAssetType : uint8 +{ + Default UMETA(DisplayName="Default"), + Store UMETA(DisplayName="Store"), + Language_Pack UMETA(DisplayName="Language Pack"), + Length UMETA(DisplayName="Invlaid"), +}; + +USTRUCT() +struct FOculusXRRedistPackage +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + bool Included = false; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString Name; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString Id; +}; + +USTRUCT() +struct FOculusXRAssetConfig +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + EOculusXRAssetType AssetType = EOculusXRAssetType::Default; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + bool Required = false; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString Name; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString Sku; +}; + +USTRUCT() +struct FOculusXRAssetConfigArray +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FOculusXRAssetConfig> ConfigArray; +}; + +/** + * + */ +UCLASS(config=Editor) +class OCULUSXREDITOR_API UOculusXRPlatformToolSettings : public UObject +{ + GENERATED_BODY() + +public: + UOculusXRPlatformToolSettings(); + + uint8 GetTargetPlatform() + { + return (uint8)OculusTargetPlatform; + } + void SetTargetPlatform(uint8 i) + { + OculusTargetPlatform = (EOculusXRPlatformTarget)i; + } + + FString GetApplicationID() + { + return (uint8)OculusTargetPlatform < OculusApplicationID.Num() ? OculusApplicationID[(uint8)OculusTargetPlatform] : ""; + } + void SetApplicationID(FString s) + { + if (OculusTargetPlatform < EOculusXRPlatformTarget::Length) + { + OculusApplicationID[(uint8)OculusTargetPlatform] = s; + } + } + + FString GetApplicationToken() + { + return (uint8)OculusTargetPlatform < OculusApplicationToken.Num() ? OculusApplicationToken[(uint8)OculusTargetPlatform] : ""; + } + void SetApplicationToken(FString s) + { + if (OculusTargetPlatform < EOculusXRPlatformTarget::Length) + { + OculusApplicationToken[(uint8)OculusTargetPlatform] = s; + } + } + + FString GetReleaseChannel() + { + return (uint8)OculusTargetPlatform < OculusReleaseChannel.Num() ? OculusReleaseChannel[(uint8)OculusTargetPlatform] : "Alpha"; + } + void SetReleaseChannel(FString s) + { + if (OculusTargetPlatform < EOculusXRPlatformTarget::Length) + { + OculusReleaseChannel[(uint8)OculusTargetPlatform] = s; + } + } + + FString GetReleaseNote() + { + return (uint8)OculusTargetPlatform < OculusReleaseNote.Num() ? OculusReleaseNote[(uint8)OculusTargetPlatform] : ""; + } + void SetReleaseNote(FString s) + { + if (OculusTargetPlatform < EOculusXRPlatformTarget::Length) + { + OculusReleaseNote[(uint8)OculusTargetPlatform] = s; + } + } + + FString GetLaunchFilePath() + { + return (uint8)OculusTargetPlatform < OculusLaunchFilePath.Num() ? OculusLaunchFilePath[(uint8)OculusTargetPlatform] : ""; + } + void SetLaunchFilePath(FString s) + { + if (OculusTargetPlatform < EOculusXRPlatformTarget::Length) + { + OculusLaunchFilePath[(uint8)OculusTargetPlatform] = s; + } + } + + EOculusXRGamepadEmulation GetRiftGamepadEmulation() + { + return OculusRiftGamepadEmulation; + } + void SetRiftGamepadEmulation(uint8 i) + { + OculusRiftGamepadEmulation = (EOculusXRGamepadEmulation)i; + } + + FString GetLanguagePacksPath() + { + return (uint8)OculusTargetPlatform < OculusLanguagePacksPath.Num() ? OculusLanguagePacksPath[(uint8)OculusTargetPlatform] : ""; + } + void SetLanguagePacksPath(FString s) + { + if (OculusTargetPlatform < EOculusXRPlatformTarget::Length) + { + OculusLanguagePacksPath[(uint8)OculusTargetPlatform] = s; + } + } + + FString GetExpansionFilesPath() + { + return (uint8)OculusTargetPlatform < OculusExpansionFilesPath.Num() ? OculusExpansionFilesPath[(uint8)OculusTargetPlatform] : ""; + } + void SetExpansionFilesPath(FString s) + { + if (OculusTargetPlatform < EOculusXRPlatformTarget::Length) + { + OculusExpansionFilesPath[(uint8)OculusTargetPlatform] = s; + } + } + + FString GetSymbolDirPath() + { + return (uint8)OculusTargetPlatform < OculusSymbolDirPath.Num() ? OculusSymbolDirPath[(uint8)OculusTargetPlatform] : ""; + } + void SetSymbolDirPath(FString s) + { + if (OculusTargetPlatform < EOculusXRPlatformTarget::Length) + { + OculusSymbolDirPath[(uint8)OculusTargetPlatform] = s; + } + } + + TArray<FOculusXRAssetConfig>* GetAssetConfigs() + { + return (uint8)OculusTargetPlatform < OculusAssetConfigs.Num() ? &OculusAssetConfigs[(uint8)OculusTargetPlatform].ConfigArray : NULL; + } + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString OculusRiftBuildDirectory; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString OculusRiftBuildVersion; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString OculusRiftLaunchParams; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + bool OculusRiftFireWallException; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString OculusRift2DLaunchPath; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString OculusRift2DLaunchParams; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FOculusXRRedistPackage> OculusRedistPackages; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + bool UploadDebugSymbols; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + bool DebugSymbolsOnly; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + FString BuildID; + +private: + UPROPERTY(config, EditAnywhere, Category = MetaXR) + EOculusXRPlatformTarget OculusTargetPlatform; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FString> OculusApplicationID; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FString> OculusApplicationToken; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FString> OculusReleaseChannel; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FString> OculusReleaseNote; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FString> OculusLaunchFilePath; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + EOculusXRGamepadEmulation OculusRiftGamepadEmulation; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FString> OculusLanguagePacksPath; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FString> OculusExpansionFilesPath; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FString> OculusSymbolDirPath; + + UPROPERTY(config, EditAnywhere, Category = MetaXR) + TArray<FOculusXRAssetConfigArray> OculusAssetConfigs; +}; diff --git a/Plugins/OculusXR/Source/OculusXREyeTracker/OculusXREyeTracker.Build.cs b/Plugins/OculusXR/Source/OculusXREyeTracker/OculusXREyeTracker.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..9233c8d7b305a250fad998780cfc9c0c4d8fdd94 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREyeTracker/OculusXREyeTracker.Build.cs @@ -0,0 +1,40 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class OculusXREyeTracker : ModuleRules + { + public OculusXREyeTracker(ReadOnlyTargetRules Target) : base(Target) + { + if (Target.Platform == UnrealTargetPlatform.Win64 || + Target.Platform == UnrealTargetPlatform.Android) + { + PrivateIncludePaths.AddRange( + new string[] { + // Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source + "OculusXRHMD/Private", + }); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "InputDevice", + "EyeTracker", + "OVRPluginXR", + "OculusXRHMD", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + } + ); + } + } + } +} diff --git a/Plugins/OculusXR/Source/OculusXREyeTracker/Private/OculusXREyeTracker.cpp b/Plugins/OculusXR/Source/OculusXREyeTracker/Private/OculusXREyeTracker.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7daa1ba181a7422ab7751b867405cf14f236fb76 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXREyeTracker/Private/OculusXREyeTracker.cpp @@ -0,0 +1,214 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#include "IEyeTrackerModule.h" +#include "EyeTrackerTypes.h" +#include "IEyeTracker.h" +#include "Modules/ModuleManager.h" + +#include "GameFramework/WorldSettings.h" +#include "Engine/World.h" +#include "IXRTrackingSystem.h" +#include "Engine/Engine.h" + +#include "OculusXRHMDModule.h" +#include "OculusXRHMDPrivate.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + +namespace OculusXRHMD +{ + class FOculusXREyeTracker : public IEyeTracker + { + public: + FOculusXREyeTracker() + { + GetUnitScaleFactorFromSettings(GWorld, WorldToMeters); + if (GEngine != nullptr) + { + OculusXRHMD = GEngine->XRSystem.Get(); + } + } + + virtual ~FOculusXREyeTracker() + { + if (bIsTrackerStarted) + { + ensureMsgf(OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StopEyeTracking()), TEXT("Cannot stop eye tracker.")); + } + } + + private: + // IEyeTracker + virtual void SetEyeTrackedPlayer(APlayerController*) override + { + unimplemented(); + } + + virtual bool GetEyeTrackerGazeData(FEyeTrackerGazeData& OutGazeData) const override + { + return ReactOnEyeTrackerState([this, &OutGazeData](const ovrpEyeGazesState& OVREyeGazesState, const FTransform& TrackingToWorld) { + OutGazeData.FixationPoint = GetFixationPoint(OVREyeGazesState); + OutGazeData.ConfidenceValue = MergeConfidence(OVREyeGazesState); + + OutGazeData.GazeDirection = TrackingToWorld.TransformVector(MergeOrientation(OVREyeGazesState).GetForwardVector()); + OutGazeData.GazeOrigin = TrackingToWorld.TransformPosition(MergePosition(OVREyeGazesState) * WorldToMeters); + }); + } + + virtual bool GetEyeTrackerStereoGazeData(FEyeTrackerStereoGazeData& OutGazeData) const override + { + return ReactOnEyeTrackerState([this, &OutGazeData](const ovrpEyeGazesState& OVREyeGazesState, const FTransform& TrackingToWorld) { + OutGazeData.FixationPoint = GetFixationPoint(OVREyeGazesState); + OutGazeData.ConfidenceValue = MergeConfidence(OVREyeGazesState); + + const auto& LeftEyePose = OVREyeGazesState.EyeGazes[ovrpEye_Left].Pose; + const auto& RightEyePose = OVREyeGazesState.EyeGazes[ovrpEye_Right].Pose; + OutGazeData.LeftEyeDirection = TrackingToWorld.TransformVector(OculusXRHMD::ToFQuat(LeftEyePose.Orientation).GetForwardVector()); + OutGazeData.RightEyeDirection = TrackingToWorld.TransformVector(OculusXRHMD::ToFQuat(RightEyePose.Orientation).GetForwardVector()); + OutGazeData.LeftEyeOrigin = TrackingToWorld.TransformPosition(OculusXRHMD::ToFVector(LeftEyePose.Position) * WorldToMeters); + OutGazeData.RightEyeOrigin = TrackingToWorld.TransformPosition(OculusXRHMD::ToFVector(RightEyePose.Position) * WorldToMeters); + }); + } + + virtual EEyeTrackerStatus GetEyeTrackerStatus() const override + { + ovrpBool IsSupported = ovrpBool_False; + ovrpBool IsEnabled = ovrpBool_False; + ovrpResult TrackingSupportedResult = FOculusXRHMDModule::GetPluginWrapper().GetEyeTrackingSupported(&IsSupported); + ovrpResult TrackingEnabledResult = FOculusXRHMDModule::GetPluginWrapper().GetEyeTrackingEnabled(&IsEnabled); + if (OVRP_SUCCESS(TrackingSupportedResult) && OVRP_SUCCESS(TrackingEnabledResult)) + { + if ((IsSupported == ovrpBool_True) && (IsEnabled == ovrpBool_True)) + { + return EEyeTrackerStatus::Tracking; + } + else if (IsSupported == ovrpBool_True) + { + return EEyeTrackerStatus::NotTracking; + } + } + + return EEyeTrackerStatus::NotConnected; + } + + virtual bool IsStereoGazeDataAvailable() const override + { + return true; + } + + private: + // FOculusXREyeTracker + template <typename ReactOnState> + bool ReactOnEyeTrackerState(ReactOnState&& React) const + { + if (!bIsTrackerStarted) + { + bIsTrackerStarted = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StartEyeTracking()); + } + + if (bIsTrackerStarted) + { + ovrpEyeGazesState OVREyeGazesState; + ovrpResult OVREyeGazesStateResult = FOculusXRHMDModule::GetPluginWrapper().GetEyeGazesState(ovrpStep_Render, OVRP_CURRENT_FRAMEINDEX, &OVREyeGazesState); + checkf(OVREyeGazesStateResult != ovrpFailure_NotYetImplemented, TEXT("Eye tracking is not implemented on this platform.")); + + if (OVRP_SUCCESS(OVREyeGazesStateResult) && IsStateValidForBothEyes(OVREyeGazesState)) + { + FTransform TrackingToWorld = OculusXRHMD ? OculusXRHMD->GetTrackingToWorldTransform() : FTransform::Identity; + React(OVREyeGazesState, TrackingToWorld); + + return true; + } + } + + return false; + } + + static float IsStateValidForBothEyes(const ovrpEyeGazesState& OVREyeGazesState) + { + return OVREyeGazesState.EyeGazes[ovrpEye_Left].IsValid && OVREyeGazesState.EyeGazes[ovrpEye_Right].IsValid; + } + + static float MergeConfidence(const ovrpEyeGazesState& OVREyeGazesState) + { + const auto& LeftEyeConfidence = OVREyeGazesState.EyeGazes[ovrpEye_Left].Confidence; + const auto& RightEyeConfidence = OVREyeGazesState.EyeGazes[ovrpEye_Right].Confidence; + return FGenericPlatformMath::Min(LeftEyeConfidence, RightEyeConfidence); + } + + /// Warn: The result of MergedOrientation is not normalized. + static FQuat MergeOrientation(const ovrpEyeGazesState& OVREyeGazesState) + { + const auto& LeftEyeOrientation = OculusXRHMD::ToFQuat(OVREyeGazesState.EyeGazes[ovrpEye_Left].Pose.Orientation); + const auto& RightEyeOrientation = OculusXRHMD::ToFQuat(OVREyeGazesState.EyeGazes[ovrpEye_Right].Pose.Orientation); + return FQuat::FastLerp(LeftEyeOrientation, RightEyeOrientation, 0.5f); + } + + static FVector MergePosition(const ovrpEyeGazesState& OVREyeGazesState) + { + const auto& LeftEyePosition = OculusXRHMD::ToFVector(OVREyeGazesState.EyeGazes[ovrpEye_Left].Pose.Position); + const auto& RightEyePosition = OculusXRHMD::ToFVector(OVREyeGazesState.EyeGazes[ovrpEye_Right].Pose.Position); + return (LeftEyePosition + RightEyePosition) / 2.f; + } + + static FVector GetFixationPoint(const ovrpEyeGazesState& OVREyeGazesState) + { + return FVector::ZeroVector; // Not supported + } + + float WorldToMeters = 100.f; + IXRTrackingSystem* OculusXRHMD = nullptr; + mutable bool bIsTrackerStarted = false; + }; +} // namespace OculusXRHMD +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + +class FOculusXREyeTrackerModule : public IEyeTrackerModule +{ +public: + static inline FOculusXREyeTrackerModule& Get() + { + return FModuleManager::LoadModuleChecked<FOculusXREyeTrackerModule>("OculusXREyeTracker"); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("OculusXREyeTracker"); + } + + virtual FString GetModuleKeyName() const override + { + return TEXT("OculusXREyeTracker"); + } + + virtual bool IsEyeTrackerConnected() const override + { +#if OCULUS_HMD_SUPPORTED_PLATFORMS + if (FOculusXRHMDModule::Get().IsOVRPluginAvailable()) + { + ovrpBool isSupported = ovrpBool_False; + ovrpResult trackingSupportedResult = FOculusXRHMDModule::GetPluginWrapper().GetEyeTrackingSupported(&isSupported); + if (OVRP_SUCCESS(trackingSupportedResult)) + { + return (isSupported == ovrpBool_True); + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return false; + } + + virtual TSharedPtr<class IEyeTracker, ESPMode::ThreadSafe> CreateEyeTracker() override + { +#if OCULUS_HMD_SUPPORTED_PLATFORMS + return TSharedPtr<class IEyeTracker, ESPMode::ThreadSafe>(new OculusXRHMD::FOculusXREyeTracker); +#else + return TSharedPtr<class IEyeTracker, ESPMode::ThreadSafe>(); +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + } +}; + +IMPLEMENT_MODULE(FOculusXREyeTrackerModule, OculusXREyeTracker) diff --git a/Plugins/OculusXR/Source/OculusXRHMD/OculusMobile_APL.xml b/Plugins/OculusXR/Source/OculusXRHMD/OculusMobile_APL.xml new file mode 100644 index 0000000000000000000000000000000000000000..3443c0f14fa968391369dac79d8087cfe53e6f5c --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/OculusMobile_APL.xml @@ -0,0 +1,450 @@ +<?xml version="1.0" encoding="utf-8"?> +<!--Oculus mobile plugin additions--> +<root xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- init section is always evaluated once per architecture --> + <init> + <log text="Oculus mobile init"/> + <setBool result="bSupported" value="false"/> + <isArch arch="armeabi-v7a"> + <setBool result="bSupported" value="true"/> + </isArch> + <isArch arch="arm64-v8a"> + <setBool result="bSupported" value="true"/> + </isArch> + + <!-- remove Oculus Signature Files by default --> + <setBool result="bRemoveOSIG" value="true"/> + + <!-- determine the XrApi libraries that need to be loaded --> + <setStringFromProperty result="XrApi" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="XrApi" default="OVRPluginOpenXR"/> + <setBoolIsEqual result="bOVRPluginOpenXR" arg1="$S(XrApi)" arg2="OVRPluginOpenXR"/> + <setBoolIsEqual result="bNativeOpenXR" arg1="$S(XrApi)" arg2="NativeOpenXR"/> + + <setBoolFromProperty + result="bFocusAware" + ini="Engine" + section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bFocusAware" + default="true"/> + + <!-- get package for oculus devices from AndroidRuntimeSettings --> + <setBoolFromPropertyContains result="bPackageForQuest" ini="Engine" section="/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" property="PackageForOculusMobile" contains="Quest"/> + <setBoolFromPropertyContains result="bPackageForQuest2" ini="Engine" section="/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" property="PackageForOculusMobile" contains="Quest2"/> + <setBoolFromPropertyContains result="bPackageForQuestPro" ini="Engine" section="/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" property="PackageForOculusMobile" contains="QuestPro"/> + <setBoolOr result="bPackageForOculusMobile" arg1="$B(bPackageForQuest)" arg2="$B(bPackageForQuest2)"/> + <setBoolOr result="bPackageForOculusMobile" arg1="$B(bPackageForOculusMobile)" arg2="$B(bPackageForQuestPro)"/> + + <!-- get supported oculus devices from OculusXRHMDRuntimeSettings --> + <setBoolFromPropertyContains result="bSupportMetaQuest" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="SupportedDevices" contains="Quest"/> + <setBoolFromPropertyContains result="bSupportMetaQuest2" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="SupportedDevices" contains="Quest2"/> + <setBoolFromPropertyContains result="bSupportMetaQuestPro" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="SupportedDevices" contains="QuestPro"/> + + <setBoolFromProperty result="bShowLaunchImage" ini="Engine" section="/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" property="bShowLaunchImage" default="false"/> + <setBoolFromProperty result="bRequiresSystemKeyboard" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bRequiresSystemKeyboard" default="false"/> + <setStringFromProperty result="HandTrackingSupport" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="HandTrackingSupport" default="ControllersOnly"/> + <setStringFromProperty result="HandTrackingFrequency" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="HandTrackingFrequency" default="LOW"/> + <setStringFromProperty result="ColorSpace" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="ColorSpace" default="Rec_709"/> + <setBoolIsEqual result="bColorSpaceUnmanaged" arg1="$S(ColorSpace)" arg2="Unmanaged"/> + <setBoolIsEqual result="bColorSpaceRec2020" arg1="$S(ColorSpace)" arg2="Rec_2020"/> + <setBoolIsEqual result="bColorSpaceRec709" arg1="$S(ColorSpace)" arg2="Rec_709"/> + <setBoolIsEqual result="bColorSpaceRiftCV1" arg1="$S(ColorSpace)" arg2="Rift_CV1"/> + <setBoolIsEqual result="bColorSpaceRiftS" arg1="$S(ColorSpace)" arg2="Rift_S"/> + <setBoolIsEqual result="bColorSpaceQuest" arg1="$S(ColorSpace)" arg2="Quest"/> + <setBoolIsEqual result="bColorSpaceP3" arg1="$S(ColorSpace)" arg2="P3"/> + <setBoolIsEqual result="bColorSpaceAdobeRGB" arg1="$S(ColorSpace)" arg2="Adobe_RGB"/> + <setBoolFromProperty result="bInsightPassthroughEnabled" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bInsightPassthroughEnabled" default="false"/> + <setBoolFromProperty result="bAnchorSupportEnabled" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bAnchorSupportEnabled" default="false"/> + <setBoolFromProperty result="bSupportEyeTrackedFoveatedRendering" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bSupportEyeTrackedFoveatedRendering" default="false"/> + + <!-- check for experimental feature support from config --> + <setBoolFromProperty result="bSupportExperimentalFeatures" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bSupportExperimentalFeatures" default="false"/> + + <setBoolFromProperty result="bBodyTrackingEnabled" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bBodyTrackingEnabled" default="false"/> + <setBoolFromProperty result="bEyeTrackingEnabled" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bEyeTrackingEnabled" default="false"/> + <setBoolFromProperty result="bFaceTrackingEnabled" ini="Engine" section="/Script/OculusXRHMD.OculusXRHMDRuntimeSettings" property="bFaceTrackingEnabled" default="false"/> + + <!-- get packaging for Oculus Mobile from ini and reset it if architecture not supported --> + <if condition="bPackageForOculusMobile"> + <true> + <if condition="bSupported"> + <true> + <if condition="Distribution"> + <true> + <setBoolFromProperty result="bRemoveOSIG" ini="Engine" section="/Script/AndroidRuntimeSettings.AndroidRuntimeSettings" property="bRemoveOSIG" default="false"/> + <if condition="bRemoveOSIG"> + <true> + <log text="Oculus mobile entitlement checks are enabled"/> + </true> + </if> + </true> + <false> + <!-- if not using entitlement checks need to keep the osig files --> + <setBool result="bRemoveOSIG" value="false"/> + </false> + </if> + </true> + <false> + <setBool result="bPackageForOculusMobile" value="false"/> + <log text="Oculus mobile not supported for this architecture, disabled."/> + </false> + </if> + </true> + </if> + + <if condition="bRemoveOSIG"> + <true> + <log text="Oculus Signature Files (osig) will be removed from APK"/> + </true> + </if> + + <!-- package for Oculus and for distribution --> + <setBool result="bOculusDistribution" value="false"/> + <if condition="bPackageForOculusMobile"> + <true> + <isDistribution> + <setBool result="bOculusDistribution" value="true"/> + <log text="Building with Oculus mobile for distribution"/> + </isDistribution> + </true> + </if> + + <!-- entitlements check if package Oculus for distribution and removing OSIGs --> + <setBoolAnd result="bEntitlementCheck" arg1="$B(bRemoveOSIG)" arg2="$B(bOculusDistribution)"/> + </init> + + <!-- optional updates applied to AndroidManifest.xml --> + <androidManifestUpdates> + <if condition="bOculusDistribution"> + <true> + <!-- distribution builds can install internal or SD card --> + <addAttribute tag="manifest" name="android:installLocation" value="auto"/> + + <!-- update the GameActivity activity --> + <loopElements tag="activity"> + <setStringFromAttribute result="activityName" tag="$" name="android:name"/> + <setBoolIsEqual result="bGameActivity" arg1="$S(activityName)" arg2="com.epicgames.unreal.GameActivity"/> + <if condition="bGameActivity"> + <true> + <!-- do not want application to show in recents --> + <addAttribute tag="$" name="android:excludeFromRecents" value="true"/> + + <!-- distribution builds should not be launched from home screen so remove LAUNCHER --> + <loopElements tag="category"> + <setStringFromAttribute result="categoryName" tag="$" name="android:name"/> + <setBoolIsEqual result="bLauncher" arg1="$S(categoryName)" arg2="android.intent.category.LAUNCHER"/> + <if condition="bLauncher"> + <true> + <removeElement tag="$"/> + </true> + </if> + </loopElements> + + <!-- add INFO intent category instead --> + <setElement result="intentInfo" value="category"/> + <addAttribute tag="$intentInfo" name="android:name" value="android.intent.category.INFO"/> + <addElement tag="intent-filter" name="intentInfo"/> + </true> + </if> + </loopElements> + </true> + </if> + <setBool result="bOculus6Dof" value="$B(bPackageForOculusMobile)" /> + <if condition="bPackageForOculusMobile"> + <true> + <if condition="bOculusDistribution"> + <false> + <!-- Add Extlib Flag --> + <setElement result="extlib" value="meta-data" /> + <addAttribute tag="$extlib" name="android:name" value="com.oculus.extlib" /> + <addAttribute tag="$extlib" name="android:value" value="true" /> + <addElement tag="application" name="extlib"/> + </false> + </if> + </true> + </if> + + <!-- Add Quest Specific Flags --> + <if condition="bOculus6Dof"> + <true> + <addFeature android:name="android.hardware.vr.headtracking" android:version="1" android:required="true" /> + + <!-- Add Hand Tracking Flag --> + <setBoolIsEqual result="bHandsOnly" arg1="$S(HandTrackingSupport)" arg2="HandsOnly"/> + <setBoolIsEqual result="bControllersAndHands" arg1="$S(HandTrackingSupport)" arg2="ControllersAndHands"/> + <setBoolOr result="bEnableHandTracking" arg1="$B(bHandsOnly)" arg2="$B(bControllersAndHands)"/> + <if condition="bEnableHandTracking"> + <true> + <addPermission android:name="com.oculus.permission.HAND_TRACKING" /> + <addFeature android:name="oculus.software.handtracking" android:required="$B(bHandsOnly)"/> + </true> + </if> + <!-- Add Passthrough flag--> + <if condition="bInsightPassthroughEnabled"> + <true> + <addFeature android:name="com.oculus.feature.PASSTHROUGH" android:required="true"/> + </true> + </if> + <!-- Add Eye Tracking flags--> + <setBool result="bEyeTrackingFeature" value="false"/> + <setBoolOr result="bEyeTrackingFeature" arg1="$B(bEyeTrackingFeature)" arg2="$B(bSupportEyeTrackedFoveatedRendering)"/> + <setBoolOr result="bEyeTrackingFeature" arg1="$B(bEyeTrackingFeature)" arg2="$B(bEyeTrackingEnabled)"/> + <!-- Check for other features that require eye tracking here --> + <if condition="bEyeTrackingFeature"> + <true> + <log text="Adding eye tracking feature and permission tags to manifest"/> + <addPermission android:name="com.oculus.permission.EYE_TRACKING" /> + <addFeature android:name="oculus.software.eye_tracking" android:required="false"/> + </true> + </if> + <!-- Add Body Tracking flags--> + <if condition="bBodyTrackingEnabled"> + <true> + <log text="Adding body tracking feature and permission tags to manifest"/> + <addPermission android:name="com.oculus.permission.BODY_TRACKING" /> + <addFeature android:name="com.oculus.software.body_tracking" android:required="true"/> + </true> + </if> + <!-- Add Face Tracking flags--> + <if condition="bFaceTrackingEnabled"> + <true> + <log text="Adding face tracking feature and permission tags to manifest"/> + <addPermission android:name="com.oculus.permission.FACE_TRACKING" /> + <addFeature android:name="oculus.software.face_tracking" android:required="true"/> + </true> + </if> + <!-- Add Experimental Features flag--> + <if condition="bSupportExperimentalFeatures"> + <true> + <log text="Adding experimental feature tag to manifest"/> + <addFeature android:name="com.oculus.experimental.enabled" android:required="true"/> + </true> + </if> + <!-- Add Flags for Spatial Anchors and Scene--> + <if condition="bAnchorSupportEnabled"> + <true> + <addPermission android:name="com.oculus.permission.USE_ANCHOR_API"/> + <addPermission android:name="com.oculus.permission.ACCESS_TRACKING_ENV" /> + <addPermission android:name="com.oculus.permission.IMPORT_EXPORT_IOT_MAP_DATA" /> + <addPermission android:name="android.permission.INTERACT_ACROSS_USERS" /> + </true> + </if> + </true> + </if> + + <!-- Add Activity Specific Flags --> + <loopElements tag="activity"> + <setStringFromAttribute result="activityName" tag="$" name="android:name"/> + <setBoolIsEqual result="bGameActivity" arg1="$S(activityName)" arg2="com.epicgames.unreal.GameActivity"/> + <if condition="bGameActivity"> + <true> + <!-- Add VR Intent Filter, Permissions, and Features --> + <if condition="bPackageForOculusMobile"> + <true> + <addFeature android:name="android.hardware.usb.host"/> + </true> + </if> + + <!-- Quest Specific Activity Tags --> + <if condition="bOculus6Dof"> + <true> + <!-- Add System Keyboard Flag --> + <if condition="bFocusAware"> + <true> + <if condition="bRequiresSystemKeyboard"> + <true> + <addFeature android:name="oculus.software.overlay_keyboard" android:required="false"/> + </true> + </if> + </true> + </if> + </true> + </if> + </true> + </if> + </loopElements> + + <!-- Add Application Specific Flags --> + <loopElements tag="application"> + <!-- Add SupportedDevices Tag --> + <setString result="devicesString" value="" /> + <if condition="bSupportMetaQuest"> + <true> + <setStringAdd result="devicesString" arg1="$S(devicesString)" arg2="quest|" /> + </true> + </if> + <if condition="bSupportMetaQuest2"> + <true> + <setStringAdd result="devicesString" arg1="$S(devicesString)" arg2="quest2|" /> + </true> + </if> + <if condition="bSupportMetaQuestPro"> + <true> + <setStringAdd result="devicesString" arg1="$S(devicesString)" arg2="cambria|" /> + </true> + </if> + <setIntLength result="devicesStringLength" source="$S(devicesString)"/> + <setBoolIsGreater result="bDevicesSupported" arg1="$I(devicesStringLength)" arg2="0"/> + <if condition="bDevicesSupported"> + <true> + <loopElements tag="meta-data"> + <setStringFromAttribute result="nameString" tag="$" name="android:name"/> + <setBoolIsEqual result="bIsSupportedDevices" arg1="$S(nameString)" arg2="com.oculus.supportedDevices"/> + <if condition="bIsSupportedDevices"> + <true> + <setStringFromAttribute result="existingDevicesString" tag="$" name="android:value"/> + <log text="Found existing Meta Quest supported devices tag: $S(existingDevicesString)" /> + <setStringAdd result="devicesString" arg1="$S(existingDevicesString)" arg2="|$S(devicesString)" /> + <setIntLength result="devicesStringLength" source="$S(devicesString)"/> + <removeElement tag="$" /> + </true> + </if> + </loopElements> + <setElement result="supportedDevices" value="meta-data" /> + <addAttribute tag="$supportedDevices" name="android:name" value="com.oculus.supportedDevices" /> + <setIntSubtract result="devicesStringLength" arg1="$I(devicesStringLength)" arg2="1"/> + <setStringSubstring result="devicesString" source="$S(devicesString)" start="0" length="$I(devicesStringLength)" /> + <log text="Adding Meta Quest supported devices tag: $S(devicesString)" /> + <addAttribute tag="$supportedDevices" name="android:value" value="$S(devicesString)" /> + <addElement tag="application" name="supportedDevices"/> + </true> + </if> + + <!-- Add Hand Tracking Frequency --> + <if condition="bEnableHandTracking"> + <true> + <setElement result="handTrackingFrequency" value="meta-data" /> + <addAttribute tag="$handTrackingFrequency" name="android:name" value="com.oculus.handtracking.frequency" /> + <addAttribute tag="$handTrackingFrequency" name="android:value" value="$S(HandTrackingFrequency)" /> + <addElement tag="application" name="handTrackingFrequency"/> + </true> + </if> + + + <!-- Add Oculus Splash Screen --> + <if condition="bShowLaunchImage"> + <true> + <setElement result="showOculusSplash" value="meta-data" /> + <addAttribute tag="$showOculusSplash" name="android:name" value="com.oculus.ossplash" /> + <addAttribute tag="$showOculusSplash" name="android:value" value="true" /> + <addElement tag="application" name="showOculusSplash"/> + + <!-- Add Oculus Splash Screen colorspace setting --> + <setElement result="oculusSplashColorspace" value="meta-data" /> + <addAttribute tag="$oculusSplashColorspace" name="android:name" value="com.oculus.ossplash.colorspace" /> + <if condition="bColorSpaceUnmanaged"> + <true> + <addAttribute tag="$oculusSplashColorspace" name="android:value" value="!Unmanaged" /> + </true> + </if> + <if condition="bColorSpaceRec2020"> + <true> + <addAttribute tag="$oculusSplashColorspace" name="android:value" value="Rec.2020" /> + </true> + </if> + <if condition="bColorSpaceRec709"> + <true> + <addAttribute tag="$oculusSplashColorspace" name="android:value" value="Rec.709" /> + </true> + </if> + <if condition="bColorSpaceRiftCV1"> + <true> + <addAttribute tag="$oculusSplashColorspace" name="android:value" value="!RiftCV1" /> + </true> + </if> + <if condition="bColorSpaceRiftS"> + <true> + <addAttribute tag="$oculusSplashColorspace" name="android:value" value="!RiftS" /> + </true> + </if> + <if condition="bColorSpaceQuest"> + <true> + <addAttribute tag="$oculusSplashColorspace" name="android:value" value="!Quest" /> + </true> + </if> + <if condition="bColorSpaceP3"> + <true> + <addAttribute tag="$oculusSplashColorspace" name="android:value" value="!P3" /> + </true> + </if> + <if condition="bColorSpaceAdobeRGB"> + <true> + <addAttribute tag="$oculusSplashColorspace" name="android:value" value="!Adobe" /> + </true> + </if> + <addElement tag="application" name="oculusSplashColorspace"/> + </true> + </if> + </loopElements> + + </androidManifestUpdates> + + <!-- optional additions to proguard --> + <proguardAdditions> + <insert> +-keep class com.oculus.** { +*; +} +-keep class android.app.** { +*; +} + </insert> + </proguardAdditions> + + <!-- optional files or directories to copy to Intermediate/Android/APK --> + <resourceCopies> + <isArch arch="arm64-v8a"> + <if condition="bOVRPluginOpenXR"> + <true> + <log text="Copying libopenxr_loader.so"/> + <copyFile src="$S(EngineDir)/Source/ThirdParty/Oculus/OculusOpenXRLoader/OculusOpenXRLoader/Lib/arm64-v8a/libopenxr_loader.so" + dst="$S(BuildDir)/libs/arm64-v8a/libopenxr_loader.so" /> + <log text="Copying OpenXR libOVRPlugin.so"/> + <copyFile src="$S(PluginDir)/../ThirdParty/OVRPlugin/OVRPlugin/Lib/arm64-v8a/OpenXR/libOVRPlugin.so" + dst="$S(BuildDir)/libs/arm64-v8a/libOVRPlugin.so" /> + </true> + </if> + <if condition="bNativeOpenXR"> + <true> + <log text="Copying libopenxr_loader.so"/> + <copyFile src="$S(EngineDir)/Source/ThirdParty/Oculus/OculusOpenXRLoader/OculusOpenXRLoader/Lib/arm64-v8a/libopenxr_loader.so" + dst="$S(BuildDir)/libs/arm64-v8a/libopenxr_loader.so" /> + </true> + </if> + </isArch> + + <copyFile src="$S(PluginDir)/../ThirdParty/OVRPlugin/OVRPlugin/ExtLibs/SystemUtils.jar" + dst="$S(BuildDir)/libs/SystemUtils.jar" /> + + <if condition="bEntitlementCheck"> + <true> + <copyFile src="$S(PluginDir)/../ThirdParty/OVRPlugin/OVRPlugin/ExtLibs/vrplatlib.jar" + dst="$S(BuildDir)/libs/vrplatlib.jar" /> + </true> + </if> + + <if condition="bRemoveOSIG"> + <true> + <deleteFiles filespec="assets/oculussig_*" /> + </true> + </if> + </resourceCopies> + + <!-- optional libraries to load in GameActivity.java before libUnreal.so --> + <soLoadLibrary> + <!-- need this if plugin enabled and supported architecture, even if not packaged for Oculus mobile --> + <if condition="bSupported"> + <true> + <if condition="bOVRPluginOpenXR"> + <true> + <loadLibrary name="openxr_loader" failmsg="openxr_loader library not loaded and may be required for Oculus VR." /> + <loadLibrary name="OVRPlugin" failmsg="OVRPlugin library not loaded and may be required for Oculus VR." /> + </true> + </if> + <if condition="bNativeOpenXR"> + <true> + <loadLibrary name="openxr_loader" failmsg="openxr_loader library not loaded and may be required for Oculus VR." /> + </true> + </if> + </true> + </if> + </soLoadLibrary> +</root> diff --git a/Plugins/OculusXR/Source/OculusXRHMD/OculusXRHMD.Build.cs b/Plugins/OculusXR/Source/OculusXRHMD/OculusXRHMD.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..e383ca63d6bedaa627bef6bc2767abdcf5d5119f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/OculusXRHMD.Build.cs @@ -0,0 +1,131 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System; +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class OculusXRHMD : ModuleRules + { + public OculusXRHMD(ReadOnlyTargetRules Target) : base(Target) + { + var EngineDir = Path.GetFullPath(Target.RelativeEnginePath); + + PrivateIncludePaths.AddRange( + new string[] { + Path.Combine(EngineDir, "Source/Runtime/Renderer/Private"), + Path.Combine(EngineDir, "Source/Runtime/Renderer/Private"), + Path.Combine(EngineDir, "Source/Runtime/OpenGLDrv/Private"), + Path.Combine(EngineDir, "Source/Runtime/Engine/Classes/Components"), + Path.Combine(EngineDir, "Source/Runtime/Engine/Classes/Kismet"), + }); + + PublicIncludePathModuleNames.AddRange( + new string[] { + "Launch", + "ProceduralMeshComponent", + "AndroidPermission" + }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "RHI", + "RHICore", + "RenderCore", + "Renderer", + "Slate", + "SlateCore", + "ImageWrapper", + "MediaAssets", + "Analytics", + "OpenGLDrv", + "VulkanRHI", + "OVRPluginXR", + "OculusOpenXRLoader", + "ProceduralMeshComponent", + "Projects", + }); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "HeadMountedDisplay", + }); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + + AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenGL"); + + if (Target.Platform == UnrealTargetPlatform.Win64) + { + // D3D + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "D3D11RHI", + "D3D12RHI", + }); + + PrivateIncludePaths.AddRange( + new string[] + { + "OculusXRMR/Public", + }); + + AddEngineThirdPartyPrivateStaticDependencies(Target, "DX11"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "DX12"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "NVAPI"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "DX11Audio"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "DirectSound"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "NVAftermath"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "IntelMetricsDiscovery"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "IntelExtensionsFramework"); + } + + // Vulkan + { + AddEngineThirdPartyPrivateStaticDependencies(Target, "Vulkan"); + } + + // OVRPlugin + { + RuntimeDependencies.Add("$(PluginDir)/Source/ThirdParty/OVRPlugin/OVRPlugin/Lib/" + Target.Platform.ToString() + "/OVRPlugin.dll"); + } + + if (Target.Platform == UnrealTargetPlatform.Win64) + { + RuntimeDependencies.Add("$(PluginDir)/Source/ThirdParty/OVRPlugin/OVRPlugin/Lib/" + Target.Platform.ToString() + "/OpenXR/OVRPlugin.dll"); + } + } + else if (Target.Platform == UnrealTargetPlatform.Android) + { + // We are not currently supporting Mixed Reality on Android, but we need to include IOculusXRMRModule.h for OCULUS_MR_SUPPORTED_PLATFORMS definition + PrivateIncludePaths.AddRange( + new string[] + { + "OculusXRMR/Public" + }); + + // Vulkan + { + AddEngineThirdPartyPrivateStaticDependencies(Target, "Vulkan"); + } + + // AndroidPlugin + { + string PluginPath = Utils.MakePathRelativeTo(ModuleDirectory, Target.RelativeEnginePath); + AdditionalPropertiesForReceipt.Add("AndroidPlugin", Path.Combine(PluginPath, "OculusMobile_APL.xml")); + } + } + } + } +} diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusStressTestShader.usf b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusStressTestShader.usf new file mode 100644 index 0000000000000000000000000000000000000000..f708b36987bd320dcf0d7e827bd74908c73b666c --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusStressTestShader.usf @@ -0,0 +1,65 @@ +#include "Common.ush" + +void MainVertexShader( + float4 InPosition : ATTRIBUTE0, + float2 InUV : ATTRIBUTE1, + out float2 OutUV : TEXCOORD0, + out float4 OutPosition : SV_POSITION + ) +{ + OutPosition = InPosition; + OutUV = InUV; +} + +Texture2D<uint> TextureParameter; + +#define Zoom 2 +#define Pan float2(0.5, 0) +#define Aspect 1 +#define Iterations int(128*PSVariables.IterationsMultiplier) +#define JuliaSeed float2(0.39, 0.2) +#define ColorScale float3(4, 5, 6) + +float ComputeValue(float2 v, float2 offset) +{ + float vxsquare = 0; + float vysquare = 0; + + int iteration = 0; + int lastIteration = Iterations; + + do + { + vxsquare = v.x * v.x; + vysquare = v.y * v.y; + + v = float2(vxsquare - vysquare, v.x * v.y * 2) + offset; + + iteration++; + + if ((lastIteration == Iterations) && (vxsquare + vysquare) > 4.0) + { + lastIteration = iteration + 1; + } + } while (iteration < lastIteration); + + return (float(iteration) - (log(log(sqrt(vxsquare + vysquare))) / log(2.0))) / float(Iterations); +} + +float4 Mandelbrot_Func(float2 texCoord : TEXCOORD0) : COLOR0 +{ + float2 v = (texCoord - 0.5) * Zoom * float2(1, Aspect) - Pan; + + float val = ComputeValue(v, v); + + return float4(sin(val * ColorScale.x), sin(val * ColorScale.y), sin(val * ColorScale.z), 1); +} + +void MainPixelShader( + in float2 uv : TEXCOORD0, + out float4 OutColor : SV_Target0 + ) +{ + OutColor = Mandelbrot_Func(uv); +} + diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRAssetManager.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRAssetManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a0f26e214fc0aefdf319f83ee974344cc54d4edc --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRAssetManager.cpp @@ -0,0 +1,264 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRAssetManager.h" +#include "OculusXRHMDPrivate.h" +#include "OculusXRHMDModule.h" +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" +#include "UObject/SoftObjectPath.h" +#include "Engine/SkeletalMesh.h" +#include "Components/SkeletalMeshComponent.h" +#include "OculusXRAssetDirectory.h" +#include "UObject/GCObject.h" + +/* FOculusAssetDirectory + *****************************************************************************/ + +enum EOculusAsset +{ + LeftTouchRiftS, + RightTouchRiftS, + OculusAssetTotal +}; + +FSoftObjectPath FOculusAssetDirectory::AssetListing[OculusAssetTotal] = +{ + FString(TEXT("/OculusXR/Meshes/LeftTouchForQuestRiftSController.LeftTouchForQuestRiftSController")), + FString(TEXT("/OculusXR/Meshes/RightTouchForQuestRiftSController.RightTouchForQuestRiftSController")) +}; + +#if WITH_EDITORONLY_DATA +class FOculusAssetRepo : public FGCObject, public TArray<UObject*> +{ +public: + // made an on-demand singleton rather than a static global, to avoid issues with FGCObject initialization + static FOculusAssetRepo& Get() + { + static FOculusAssetRepo AssetRepository; + return AssetRepository; + } + + UObject* LoadAndAdd(const FSoftObjectPath& AssetPath) + { + UObject* AssetObj = AssetPath.TryLoad(); + if (AssetObj != nullptr) + { + AddUnique(AssetObj); + } + return AssetObj; + } + +public: + //~ FGCObject interface + virtual void AddReferencedObjects(FReferenceCollector& Collector) override + { + Collector.AddReferencedObjects(*this); + } + virtual FString GetReferencerName() const override + { + return TEXT("FOculusAssetRepo"); + } +}; + +void FOculusAssetDirectory::LoadForCook() +{ + FOculusAssetRepo& AssetRepro = FOculusAssetRepo::Get(); + for (int32 AssetIndex = 0; AssetIndex < UE_ARRAY_COUNT(FOculusAssetDirectory::AssetListing); ++AssetIndex) + { + AssetRepro.LoadAndAdd(FOculusAssetDirectory::AssetListing[AssetIndex]); + } +} + +void FOculusAssetDirectory::ReleaseAll() +{ + FOculusAssetRepo::Get().Empty(); +} +#endif // WITH_EDITORONLY_DATA + + +/* OculusAssetManager_Impl + *****************************************************************************/ + +namespace OculusAssetManager_Impl +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + struct FRenderableDevice + { + ovrpNode OVRNode; + ovrpSystemHeadset MinDeviceRange; + ovrpSystemHeadset MaxDeviceRange; + FSoftObjectPath MeshAssetRef; + }; + + static FRenderableDevice RenderableDevices[] = + { +#if PLATFORM_ANDROID + { ovrpNode_HandLeft, ovrpSystemHeadset_Oculus_Quest, ovrpSystemHeadset_Oculus_Quest_2, FOculusAssetDirectory::AssetListing[LeftTouchRiftS] }, + { ovrpNode_HandRight, ovrpSystemHeadset_Oculus_Quest, ovrpSystemHeadset_Oculus_Quest_2, FOculusAssetDirectory::AssetListing[RightTouchRiftS] }, +#else + { ovrpNode_HandLeft, ovrpSystemHeadset_Rift_S, ovrpSystemHeadset_Oculus_Link_Quest_2, FOculusAssetDirectory::AssetListing[LeftTouchRiftS] }, + { ovrpNode_HandRight, ovrpSystemHeadset_Rift_S, ovrpSystemHeadset_Oculus_Link_Quest_2, FOculusAssetDirectory::AssetListing[RightTouchRiftS] }, +#endif + }; + + static uint32 RenderableDeviceCount = sizeof(RenderableDevices) / sizeof(RenderableDevices[0]); +#endif // #if OCULUS_HMD_SUPPORTED_PLATFORMS + + static UObject* FindDeviceMesh(const int32 DeviceID); +}; + + +static UObject* OculusAssetManager_Impl::FindDeviceMesh(const int32 DeviceID) +{ + UObject* DeviceMesh = nullptr; +#if OCULUS_HMD_SUPPORTED_PLATFORMS + const ovrpNode DeviceOVRNode = OculusXRHMD::ToOvrpNode(DeviceID); + + bool bUseSystemHeadsetType = false; + ovrpSystemHeadset HeadsetType; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetSystemHeadsetType2(&HeadsetType))) + { + bUseSystemHeadsetType = true; + } + + if (DeviceOVRNode != ovrpNode_None) + { + for (uint32 DeviceIndex = 0; DeviceIndex < RenderableDeviceCount; ++DeviceIndex) + { + const FRenderableDevice& RenderableDevice = RenderableDevices[DeviceIndex]; + if (RenderableDevice.OVRNode == DeviceOVRNode) + { + // If we have information about the current headset, load the model based of the headset information, otherwise load defaults. + if (bUseSystemHeadsetType) + { + if (HeadsetType >= RenderableDevice.MinDeviceRange && HeadsetType <= RenderableDevice.MaxDeviceRange) + { + DeviceMesh = RenderableDevice.MeshAssetRef.TryLoad(); + break; + } + } + else + { + DeviceMesh = RenderableDevice.MeshAssetRef.TryLoad(); + break; + } + } + } + } +#endif + return DeviceMesh; +} + +/* FOculusAssetManager +*****************************************************************************/ + +FOculusAssetManager::FOculusAssetManager() +{ + IModularFeatures::Get().RegisterModularFeature(IXRSystemAssets::GetModularFeatureName(), this); + + ResourceHolder = NewObject<UOculusXRResourceHolder>(); + ResourceHolder->AddToRoot(); +} + +FOculusAssetManager::~FOculusAssetManager() +{ + if (ResourceHolder) + { + ResourceHolder->ConditionalBeginDestroy(); + ResourceHolder = NULL; + } + + IModularFeatures::Get().UnregisterModularFeature(IXRSystemAssets::GetModularFeatureName(), this); +} + +bool FOculusAssetManager::EnumerateRenderableDevices(TArray<int32>& DeviceListOut) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + using namespace OculusAssetManager_Impl; + DeviceListOut.Empty(RenderableDeviceCount); + + for (uint32 DeviceIndex = 0; DeviceIndex < RenderableDeviceCount; ++DeviceIndex) + { + const FRenderableDevice& RenderableDevice = RenderableDevices[DeviceIndex]; + + const int32 ExternalDeviceId = OculusXRHMD::ToExternalDeviceId(RenderableDevice.OVRNode); + DeviceListOut.Add(ExternalDeviceId); + } + + return true; +#else + return false; +#endif +} + +int32 FOculusAssetManager::GetDeviceId(EControllerHand ControllerHand) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + ovrpNode ControllerOVRNode = ovrpNode_None; + + switch (ControllerHand) + { + case EControllerHand::AnyHand: + // @TODO: maybe check if the right is tracking, if not choose left (if tracking)? + case EControllerHand::Right: + ControllerOVRNode = ovrpNode_HandRight; + break; + case EControllerHand::Left: + ControllerOVRNode = ovrpNode_HandLeft; + break; + + case EControllerHand::ExternalCamera: + ControllerOVRNode = ovrpNode_TrackerZero; + break; +// case EControllerHand::Special_1: +// ControllerOVRNode = ovrpNode_TrackerOne; +// break; +// case EControllerHand::Special_2: +// ControllerOVRNode = ovrpNode_TrackerTwo; +// break; +// case EControllerHand::Special_3: +// ControllerOVRNode = ovrpNode_TrackerThree; +// break; + +// case EControllerHand::Special_4: +// ControllerOVRNode = ovrpNode_DeviceObjectZero; +// break; + + default: + // ControllerOVRNode = ovrpNode_None => returns -1 + break; + } + return OculusXRHMD::ToExternalDeviceId(ControllerOVRNode); +#else + return INDEX_NONE; +#endif +} + +UPrimitiveComponent* FOculusAssetManager::CreateRenderComponent(const int32 DeviceId, AActor* Owner, EObjectFlags Flags, const bool /*bForceSynchronous*/, const FXRComponentLoadComplete& OnLoadComplete) +{ + UPrimitiveComponent* NewRenderComponent = nullptr; + if (UObject* DeviceMesh = OculusAssetManager_Impl::FindDeviceMesh(DeviceId)) + { + if (UStaticMesh* AsStaticMesh = Cast<UStaticMesh>(DeviceMesh)) + { + const FName ComponentName = MakeUniqueObjectName(Owner, UStaticMeshComponent::StaticClass(), *FString::Printf(TEXT("%s_Device%d"), TEXT("Oculus"), DeviceId)); + UStaticMeshComponent* MeshComponent = NewObject<UStaticMeshComponent>(Owner, ComponentName, Flags); + + MeshComponent->SetStaticMesh(AsStaticMesh); + NewRenderComponent = MeshComponent; + } + else if (USkeletalMesh* AsSkeletalMesh = Cast<USkeletalMesh>(DeviceMesh)) + { + const FName ComponentName = MakeUniqueObjectName(Owner, USkeletalMeshComponent::StaticClass(), *FString::Printf(TEXT("%s_Device%d"), TEXT("Oculus"), DeviceId)); + USkeletalMeshComponent* SkelMeshComponent = NewObject<USkeletalMeshComponent>(Owner, ComponentName, Flags); + + SkelMeshComponent->SetSkeletalMesh(AsSkeletalMesh); + NewRenderComponent = SkelMeshComponent; + } + NewRenderComponent->SetCollisionEnabled(ECollisionEnabled::NoCollision); + } + + OnLoadComplete.ExecuteIfBound(NewRenderComponent); + return NewRenderComponent; +} + diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRAssetManager.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRAssetManager.h new file mode 100644 index 0000000000000000000000000000000000000000..6eea0a1684ad216c78f9a6ae0a30dd03ba1fae0e --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRAssetManager.h @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "IXRSystemAssets.h" +#include "OculusXRResourceHolder.h" +#include "UObject/SoftObjectPtr.h" + +/** + * + */ +class FOculusAssetManager : public IXRSystemAssets +{ +public: + FOculusAssetManager(); + virtual ~FOculusAssetManager(); + +public: + UOculusXRResourceHolder* GetResourceHolder() { return ResourceHolder; } + + //~ IXRSystemAssets interface + + virtual bool EnumerateRenderableDevices(TArray<int32>& DeviceListOut) override; + virtual int32 GetDeviceId(EControllerHand ControllerHand) override; + virtual UPrimitiveComponent* CreateRenderComponent(const int32 DeviceId, AActor* Owner, EObjectFlags Flags, const bool bForceSynchronous, const FXRComponentLoadComplete& OnLoadComplete) override; + +protected: + UOculusXRResourceHolder* ResourceHolder; +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRDelegates.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRDelegates.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f77cf15828fc73aacb07110d5e94e6ce7974bc3f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRDelegates.cpp @@ -0,0 +1,7 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRDelegates.h" + +FOculusEventDelegates::FOculusDisplayRefreshRateChangedEvent FOculusEventDelegates::OculusDisplayRefreshRateChanged; + +FOculusEventDelegates::FOculusEyeTrackingStateChangedEvent FOculusEventDelegates::OculusEyeTrackingStateChanged; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRDelegates.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRDelegates.h new file mode 100644 index 0000000000000000000000000000000000000000..dd8d06399dccbfd8cb6ff201726fecb2da6e6192 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRDelegates.h @@ -0,0 +1,18 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreTypes.h" +#include "Delegates/Delegate.h" + +class FOculusEventDelegates +{ +public: + /** When the display refresh rate is changed */ + DECLARE_MULTICAST_DELEGATE_TwoParams(FOculusDisplayRefreshRateChangedEvent, float /*fromRefreshRate*/, float /*toRefreshRate*/); + static FOculusDisplayRefreshRateChangedEvent OculusDisplayRefreshRateChanged; + + /** When the eye tracking status changes */ + DECLARE_MULTICAST_DELEGATE_OneParam(FOculusEyeTrackingStateChangedEvent, bool /*bIsEyeTrackingOn*/); + static FOculusEyeTrackingStateChangedEvent OculusEyeTrackingStateChanged; +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXREventComponent.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXREventComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7405eed726d2b875264c85667f369bd50f37c2de --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXREventComponent.cpp @@ -0,0 +1,20 @@ +// Copyright 1998-2020 Epic Games, Inc. All Rights Reserved. +#include "OculusXREventComponent.h" +#include "OculusXRHMD.h" +#include "OculusXRDelegates.h" + +void UOculusXREventComponent::OnRegister() +{ + Super::OnRegister(); + + FOculusEventDelegates::OculusDisplayRefreshRateChanged.AddUObject(this, &UOculusXREventComponent::OculusDisplayRefreshRateChanged_Handler); + FOculusEventDelegates::OculusEyeTrackingStateChanged.AddUObject(this, &UOculusXREventComponent::OculusEyeTrackingStateChanged_Handler); +} + +void UOculusXREventComponent::OnUnregister() +{ + Super::OnUnregister(); + + FOculusEventDelegates::OculusDisplayRefreshRateChanged.RemoveAll(this); + FOculusEventDelegates::OculusEyeTrackingStateChanged.RemoveAll(this); +} diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRFunctionLibrary.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRFunctionLibrary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..27964b29204e53bf1e09c69674b679c84f39c4a8 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRFunctionLibrary.cpp @@ -0,0 +1,868 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRFunctionLibrary.h" +#include "OculusXRHMDPrivate.h" +#include "OculusXRHMD.h" +#include "Logging/MessageLog.h" + +#define LOCTEXT_NAMESPACE "OculusFunctionLibrary" + +//------------------------------------------------------------------------------------------------- +// UOculusXRFunctionLibrary +//------------------------------------------------------------------------------------------------- + +UOculusXRFunctionLibrary::UOculusXRFunctionLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +OculusXRHMD::FOculusXRHMD* UOculusXRFunctionLibrary::GetOculusXRHMD() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + if (GEngine && GEngine->XRSystem.IsValid()) + { + if (GEngine->XRSystem->GetSystemName() == OculusXRHMD::FOculusXRHMD::OculusSystemName) + { + return static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem.Get()); + } + } +#endif + return nullptr; +} + +void UOculusXRFunctionLibrary::GetPose(FRotator& DeviceRotation, FVector& DevicePosition, FVector& NeckPosition, bool bUseOrienationForPlayerCamera, bool bUsePositionForPlayerCamera, const FVector PositionScale) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD && OculusXRHMD->IsHeadTrackingAllowed()) + { + FQuat HeadOrientation = FQuat::Identity; + FVector HeadPosition = FVector::ZeroVector; + + OculusXRHMD->GetCurrentPose(OculusXRHMD->HMDDeviceId, HeadOrientation, HeadPosition); + + DeviceRotation = HeadOrientation.Rotator(); + DevicePosition = HeadPosition; + NeckPosition = OculusXRHMD->GetNeckPosition(HeadOrientation, HeadPosition); + } + else +#endif // #if OCULUS_HMD_SUPPORTED_PLATFORMS + { + DeviceRotation = FRotator::ZeroRotator; + DevicePosition = FVector::ZeroVector; + NeckPosition = FVector::ZeroVector; + } +} + +void UOculusXRFunctionLibrary::SetBaseRotationAndBaseOffsetInMeters(FRotator Rotation, FVector BaseOffsetInMeters, EOrientPositionSelector::Type Options) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + if ((Options == EOrientPositionSelector::Orientation) || (Options == EOrientPositionSelector::OrientationAndPosition)) + { + OculusXRHMD->SetBaseRotation(Rotation); + } + if ((Options == EOrientPositionSelector::Position) || (Options == EOrientPositionSelector::OrientationAndPosition)) + { + OculusXRHMD->SetBaseOffsetInMeters(BaseOffsetInMeters); + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +void UOculusXRFunctionLibrary::GetBaseRotationAndBaseOffsetInMeters(FRotator& OutRotation, FVector& OutBaseOffsetInMeters) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + OutRotation = OculusXRHMD->GetBaseRotation(); + OutBaseOffsetInMeters = OculusXRHMD->GetBaseOffsetInMeters(); + } + else + { + OutRotation = FRotator::ZeroRotator; + OutBaseOffsetInMeters = FVector::ZeroVector; + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +void UOculusXRFunctionLibrary::GetRawSensorData(FVector& AngularAcceleration, FVector& LinearAcceleration, FVector& AngularVelocity, FVector& LinearVelocity, float& TimeInSeconds, EOculusXRTrackedDeviceType DeviceType) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr && OculusXRHMD->IsHMDActive()) + { + ovrpPoseStatef state; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, OVRP_CURRENT_FRAMEINDEX, OculusXRHMD::ToOvrpNode(DeviceType), &state))) + { + AngularAcceleration = OculusXRHMD::ToFVector(state.AngularAcceleration); + LinearAcceleration = OculusXRHMD::ToFVector(state.Acceleration); + AngularVelocity = OculusXRHMD::ToFVector(state.AngularVelocity); + LinearVelocity = OculusXRHMD::ToFVector(state.Velocity); + TimeInSeconds = state.Time; + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +bool UOculusXRFunctionLibrary::IsDeviceTracked(EOculusXRTrackedDeviceType DeviceType) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr && OculusXRHMD->IsHMDActive()) + { + ovrpBool Present; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePresent2(OculusXRHMD::ToOvrpNode(DeviceType), &Present))) + { + return Present != ovrpBool_False; + } + else + { + return false; + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return false; +} + +void UOculusXRFunctionLibrary::GetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel& CpuPerfLevel, EOculusXRProcessorPerformanceLevel& GpuPerfLevel) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr && OculusXRHMD->IsHMDActive()) + { + OculusXRHMD->GetSuggestedCpuAndGpuPerformanceLevels(CpuPerfLevel, GpuPerfLevel); + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +void UOculusXRFunctionLibrary::SetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel CpuPerfLevel, EOculusXRProcessorPerformanceLevel GpuPerfLevel) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr && OculusXRHMD->IsHMDActive()) + { + OculusXRHMD->SetSuggestedCpuAndGpuPerformanceLevels(CpuPerfLevel, GpuPerfLevel); + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +void UOculusXRFunctionLibrary::SetCPUAndGPULevels(int CPULevel, int GPULevel) +{ + // Deprecated. Please use Get/SetSuggestedCpuAndGpuPerformanceLevels instead. +} + +bool UOculusXRFunctionLibrary::GetUserProfile(FOculusXRHmdUserProfile& Profile) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + OculusXRHMD::FOculusXRHMD::UserProfile Data; + if (OculusXRHMD->GetUserProfile(Data)) + { + Profile.Name = ""; + Profile.Gender = "Unknown"; + Profile.PlayerHeight = 0.0f; + Profile.EyeHeight = Data.EyeHeight; + Profile.IPD = Data.IPD; + Profile.NeckToEyeDistance = FVector2D(Data.EyeDepth, 0.0f); + return true; + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return false; +} + +void UOculusXRFunctionLibrary::SetBaseRotationAndPositionOffset(FRotator BaseRot, FVector PosOffset, EOrientPositionSelector::Type Options) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + if (Options == EOrientPositionSelector::Orientation || Options == EOrientPositionSelector::OrientationAndPosition) + { + OculusXRHMD->SetBaseRotation(BaseRot); + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +void UOculusXRFunctionLibrary::GetBaseRotationAndPositionOffset(FRotator& OutRot, FVector& OutPosOffset) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + OutRot = OculusXRHMD->GetBaseRotation(); + OutPosOffset = FVector::ZeroVector; + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +void UOculusXRFunctionLibrary::AddLoadingSplashScreen(class UTexture2D* Texture, FVector TranslationInMeters, FRotator Rotation, FVector2D SizeInMeters, FRotator DeltaRotation, bool bClearBeforeAdd) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + OculusXRHMD::FSplash* Splash = OculusXRHMD->GetSplash(); + if (Splash) + { + if (bClearBeforeAdd) + { + Splash->ClearSplashes(); + } + + FOculusXRSplashDesc Desc; + Desc.LoadingTexture = Texture; + Desc.QuadSizeInMeters = SizeInMeters; + Desc.TransformInMeters = FTransform(Rotation, TranslationInMeters); + Desc.DeltaRotation = FQuat(DeltaRotation); + Splash->AddSplash(Desc); + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +void UOculusXRFunctionLibrary::ClearLoadingSplashScreens() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + OculusXRHMD::FSplash* Splash = OculusXRHMD->GetSplash(); + if (Splash) + { + Splash->ClearSplashes(); + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +bool UOculusXRFunctionLibrary::HasInputFocus() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + const OculusXRHMD::FOculusXRHMD* const OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr && OculusXRHMD->IsHMDActive()) + { + ovrpBool HasFocus; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppHasInputFocus(&HasFocus))) + { + return HasFocus != ovrpBool_False; + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return false; +} + +bool UOculusXRFunctionLibrary::HasSystemOverlayPresent() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + const OculusXRHMD::FOculusXRHMD* const OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr && OculusXRHMD->IsHMDActive()) + { + ovrpBool HasFocus; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppHasInputFocus(&HasFocus))) + { + return HasFocus == ovrpBool_False; + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return false; +} + +void UOculusXRFunctionLibrary::GetGPUUtilization(bool& IsGPUAvailable, float& GPUUtilization) +{ + IsGPUAvailable = false; + GPUUtilization = 0.0f; + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + const OculusXRHMD::FOculusXRHMD* const OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpBool GPUAvailable; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetGPUUtilSupported(&GPUAvailable))) + { + IsGPUAvailable = (GPUAvailable != ovrpBool_False); + FOculusXRHMDModule::GetPluginWrapper().GetGPUUtilLevel(&GPUUtilization); + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +float UOculusXRFunctionLibrary::GetGPUFrameTime() +{ + float frameTime = 0; +#if OCULUS_HMD_SUPPORTED_PLATFORMS + const OculusXRHMD::FOculusXRHMD* const OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetGPUFrameTime(&frameTime))) + { + return frameTime; + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return 0.0f; +} + +EOculusXRFoveatedRenderingMethod UOculusXRFunctionLibrary::GetFoveatedRenderingMethod() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpBool enabled; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetFoveationEyeTracked(&enabled))) + { + return enabled == ovrpBool_True ? EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering : EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering; + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering; +} + +void UOculusXRFunctionLibrary::SetFoveatedRenderingMethod(EOculusXRFoveatedRenderingMethod Method) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + OculusXRHMD->SetFoveatedRenderingMethod(Method); + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +void UOculusXRFunctionLibrary::SetFoveatedRenderingLevel(EOculusXRFoveatedRenderingLevel level, bool isDynamic) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + OculusXRHMD->SetFoveatedRenderingLevel(level, isDynamic); + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +EOculusXRFoveatedRenderingLevel UOculusXRFunctionLibrary::GetFoveatedRenderingLevel() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpTiledMultiResLevel Lvl; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetTiledMultiResLevel(&Lvl))) + { + return (EOculusXRFoveatedRenderingLevel)Lvl; + } + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return EOculusXRFoveatedRenderingLevel::Off; +} + +bool UOculusXRFunctionLibrary::GetEyeTrackedFoveatedRenderingSupported() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + // Always return false on other engine releases, since they don't have FDM offset support +#ifdef WITH_OCULUS_BRANCH + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpBool Supported; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetFoveationEyeTrackedSupported(&Supported))) + { + return Supported == ovrpBool_True; + } + } +#endif // WITH_OCULUS_BRANCH +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return false; +} + +FString UOculusXRFunctionLibrary::GetDeviceName() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + const char* NameString; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetSystemProductName2(&NameString)) && NameString) + { + return FString(NameString); + } + } +#endif + return FString(); +} + +EOculusXRDeviceType UOculusXRFunctionLibrary::GetDeviceType() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + if (OculusXRHMD->GetSettings()) + { + switch (OculusXRHMD->GetSettings()->SystemHeadset) { + case ovrpSystemHeadset_Oculus_Quest: + return EOculusXRDeviceType::OculusQuest; + case ovrpSystemHeadset_Oculus_Quest_2: + return EOculusXRDeviceType::OculusQuest2; + case ovrpSystemHeadset_Meta_Quest_Pro: + return EOculusXRDeviceType::MetaQuestPro; + case ovrpSystemHeadset_Rift_CV1: + return EOculusXRDeviceType::Rift; + case ovrpSystemHeadset_Rift_S: + return EOculusXRDeviceType::Rift_S; + case ovrpSystemHeadset_Oculus_Link_Quest: + return EOculusXRDeviceType::Quest_Link; + case ovrpSystemHeadset_Oculus_Link_Quest_2: + return EOculusXRDeviceType::Quest2_Link; + case ovrpSystemHeadset_Meta_Link_Quest_Pro: + return EOculusXRDeviceType::MetaQuestProLink; + default: + break; + } + } + } +#endif + return EOculusXRDeviceType::OculusUnknown; +} + +EOculusXRControllerType UOculusXRFunctionLibrary::GetControllerType(EControllerHand deviceHand) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + auto getOVRPHand = [](EControllerHand hand) { + switch (hand) + { + case EControllerHand::Left: + return ovrpHand::ovrpHand_Left; + case EControllerHand::Right: + return ovrpHand::ovrpHand_Right; + default: + return ovrpHand::ovrpHand_None; + } + return ovrpHand::ovrpHand_None; + }; + + auto getEControllerType = [](ovrpInteractionProfile profile) { + switch (profile) + { + case ovrpInteractionProfile::ovrpInteractionProfile_Touch: + return EOculusXRControllerType::MetaQuestTouch; + case ovrpInteractionProfile::ovrpInteractionProfile_TouchPro: + return EOculusXRControllerType::MetaQuestTouchPro; + default: + return EOculusXRControllerType::None; + } + return EOculusXRControllerType::None; + }; + + ovrpInteractionProfile interactionProfile = ovrpInteractionProfile::ovrpInteractionProfile_None; + ovrpHand hand = getOVRPHand(deviceHand); + if(hand == ovrpHand::ovrpHand_None) + return EOculusXRControllerType::Unknown; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetCurrentInteractionProfile(hand, &interactionProfile))) + { + return getEControllerType(interactionProfile); + } + return EOculusXRControllerType::Unknown; +#endif + return EOculusXRControllerType::Unknown; +} + +TArray<float> UOculusXRFunctionLibrary::GetAvailableDisplayFrequencies() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + int NumberOfFrequencies; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetSystemDisplayAvailableFrequencies(NULL, &NumberOfFrequencies))) + { + TArray<float> freqArray; + freqArray.SetNum(NumberOfFrequencies); + FOculusXRHMDModule::GetPluginWrapper().GetSystemDisplayAvailableFrequencies(freqArray.GetData(), &NumberOfFrequencies); + return freqArray; + } + } +#endif + return TArray<float>(); +} + +float UOculusXRFunctionLibrary::GetCurrentDisplayFrequency() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + float Frequency; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetSystemDisplayFrequency2(&Frequency))) + { + return Frequency; + } + } +#endif + return 0.0f; +} + +void UOculusXRFunctionLibrary::SetDisplayFrequency(float RequestedFrequency) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + FOculusXRHMDModule::GetPluginWrapper().SetSystemDisplayFrequency(RequestedFrequency); + } +#endif +} + +void UOculusXRFunctionLibrary::EnablePositionTracking(bool bPositionTracking) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + FOculusXRHMDModule::GetPluginWrapper().SetTrackingPositionEnabled2(bPositionTracking); + } +#endif +} + + +void UOculusXRFunctionLibrary::EnableOrientationTracking(bool bOrientationTracking) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + FOculusXRHMDModule::GetPluginWrapper().SetTrackingOrientationEnabled2(bOrientationTracking); + } +#endif +} + +void UOculusXRFunctionLibrary::SetColorScaleAndOffset(FLinearColor ColorScale, FLinearColor ColorOffset, bool bApplyToAllLayers) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + OculusXRHMD->SetColorScaleAndOffset(ColorScale, ColorOffset, bApplyToAllLayers); + } +#endif +} + +class IStereoLayers* UOculusXRFunctionLibrary::GetStereoLayers() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + return OculusXRHMD; + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return nullptr; +} + +/** Helper that converts EOculusXRBoundaryType to ovrpBoundaryType */ +#if OCULUS_HMD_SUPPORTED_PLATFORMS +static ovrpBoundaryType ToOvrpBoundaryType(EOculusXRBoundaryType Source) +{ + switch (Source) + { + case EOculusXRBoundaryType::Boundary_PlayArea: + return ovrpBoundary_PlayArea; + + case EOculusXRBoundaryType::Boundary_Outer: + default: + return ovrpBoundary_Outer; + } +} +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + +bool UOculusXRFunctionLibrary::IsGuardianConfigured() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpBool boundaryConfigured; + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetBoundaryConfigured2(&boundaryConfigured)) && boundaryConfigured; + } +#endif + return false; +} + +bool UOculusXRFunctionLibrary::IsGuardianDisplayed() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpBool boundaryVisible; + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetBoundaryVisible2(&boundaryVisible)) && boundaryVisible; + } +#endif + return false; +} + +TArray<FVector> UOculusXRFunctionLibrary::GetGuardianPoints(EOculusXRBoundaryType BoundaryType, bool UsePawnSpace /* = false */) +{ + TArray<FVector> BoundaryPointList; +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpBoundaryType obt = ToOvrpBoundaryType(BoundaryType); + int NumPoints = 0; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetBoundaryGeometry3(obt, NULL, &NumPoints))) + { + //allocate points + const int BufferSize = NumPoints; + ovrpVector3f* BoundaryPoints = new ovrpVector3f[BufferSize]; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetBoundaryGeometry3(obt, BoundaryPoints, &NumPoints))) + { + NumPoints = FMath::Min(BufferSize, NumPoints); + check(NumPoints <= BufferSize); // For static analyzer + BoundaryPointList.Reserve(NumPoints); + + for (int i = 0; i < NumPoints; i++) + { + FVector point; + if (UsePawnSpace) + { + point = OculusXRHMD->ConvertVector_M2U(BoundaryPoints[i]); + } + else + { + point = OculusXRHMD->ScaleAndMovePointWithPlayer(BoundaryPoints[i]); + } + BoundaryPointList.Add(point); + } + } + + delete[] BoundaryPoints; + } + } +#endif + return BoundaryPointList; +} + +FVector UOculusXRFunctionLibrary::GetGuardianDimensions(EOculusXRBoundaryType BoundaryType) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpBoundaryType obt = ToOvrpBoundaryType(BoundaryType); + ovrpVector3f Dimensions; + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetBoundaryDimensions2(obt, &Dimensions))) + return FVector::ZeroVector; + + Dimensions.z *= -1.0; + return OculusXRHMD->ConvertVector_M2U(Dimensions); + } +#endif + return FVector::ZeroVector; +} + +FTransform UOculusXRFunctionLibrary::GetPlayAreaTransform() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + int NumPoints = 4; + ovrpVector3f BoundaryPoints[4]; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetBoundaryGeometry3(ovrpBoundary_PlayArea, BoundaryPoints, &NumPoints))) + { + FVector ConvertedPoints[4]; + + for (int i = 0; i < NumPoints; i++) + { + ConvertedPoints[i] = OculusXRHMD->ScaleAndMovePointWithPlayer(BoundaryPoints[i]); + } + + float metersScale = OculusXRHMD->GetWorldToMetersScale(); + + FVector Edge = ConvertedPoints[1] - ConvertedPoints[0]; + float Angle = FMath::Acos((Edge).GetSafeNormal() | FVector::RightVector); + FQuat Rotation(FVector::UpVector, Edge.X < 0 ? Angle : -Angle); + + FVector Position = (ConvertedPoints[0] + ConvertedPoints[1] + ConvertedPoints[2] + ConvertedPoints[3]) / 4; + FVector Scale(FVector::Distance(ConvertedPoints[3], ConvertedPoints[0]) / metersScale, FVector::Distance(ConvertedPoints[1], ConvertedPoints[0]) / metersScale, 1.0); + + return FTransform(Rotation, Position, Scale); + } + } +#endif + return FTransform(); +} + +FOculusXRGuardianTestResult UOculusXRFunctionLibrary::GetPointGuardianIntersection(const FVector Point, EOculusXRBoundaryType BoundaryType) +{ + FOculusXRGuardianTestResult InteractionInfo; + memset(&InteractionInfo, 0, sizeof(FOculusXRGuardianTestResult)); + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpVector3f OvrpPoint = OculusXRHMD->WorldLocationToOculusPoint(Point); + ovrpBoundaryType OvrpBoundaryType = ToOvrpBoundaryType(BoundaryType); + ovrpBoundaryTestResult InteractionResult; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().TestBoundaryPoint2(OvrpPoint, OvrpBoundaryType, &InteractionResult))) + { + InteractionInfo.IsTriggering = (InteractionResult.IsTriggering != 0); + InteractionInfo.ClosestDistance = OculusXRHMD->ConvertFloat_M2U(InteractionResult.ClosestDistance); + InteractionInfo.ClosestPoint = OculusXRHMD->ScaleAndMovePointWithPlayer(InteractionResult.ClosestPoint); + InteractionInfo.ClosestPointNormal = OculusXRHMD->ConvertVector_M2U(InteractionResult.ClosestPointNormal); + InteractionInfo.DeviceType = EOculusXRTrackedDeviceType::None; + } + } +#endif + + return InteractionInfo; +} + +FOculusXRGuardianTestResult UOculusXRFunctionLibrary::GetNodeGuardianIntersection(EOculusXRTrackedDeviceType DeviceType, EOculusXRBoundaryType BoundaryType) +{ + FOculusXRGuardianTestResult InteractionInfo; + memset(&InteractionInfo, 0, sizeof(FOculusXRGuardianTestResult)); + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpNode OvrpNode = OculusXRHMD::ToOvrpNode(DeviceType); + ovrpBoundaryType OvrpBoundaryType = ToOvrpBoundaryType(BoundaryType); + ovrpBoundaryTestResult TestResult; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().TestBoundaryNode2(OvrpNode, ovrpBoundary_PlayArea, &TestResult)) && TestResult.IsTriggering) + { + InteractionInfo.IsTriggering = true; + InteractionInfo.DeviceType = OculusXRHMD::ToEOculusXRTrackedDeviceType(OvrpNode); + InteractionInfo.ClosestDistance = OculusXRHMD->ConvertFloat_M2U(TestResult.ClosestDistance); + InteractionInfo.ClosestPoint = OculusXRHMD->ScaleAndMovePointWithPlayer(TestResult.ClosestPoint); + InteractionInfo.ClosestPointNormal = OculusXRHMD->ConvertVector_M2U(TestResult.ClosestPointNormal); + } + } +#endif + + return InteractionInfo; +} + +void UOculusXRFunctionLibrary::SetGuardianVisibility(bool GuardianVisible) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + FOculusXRHMDModule::GetPluginWrapper().SetBoundaryVisible2(GuardianVisible); + } +#endif +} + +bool UOculusXRFunctionLibrary::GetSystemHmd3DofModeEnabled() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpBool enabled; + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetSystemHmd3DofModeEnabled(&enabled)) && enabled; + } +#endif + return false; +} + +EOculusXRColorSpace UOculusXRFunctionLibrary::GetHmdColorDesc() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpColorSpace HmdColorSpace; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetHmdColorDesc(&HmdColorSpace))) + { + return (EOculusXRColorSpace)HmdColorSpace; + } + } +#endif + return EOculusXRColorSpace::Unknown; +} + +void UOculusXRFunctionLibrary::SetClientColorDesc(EOculusXRColorSpace ColorSpace) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GetOculusXRHMD(); + if (OculusXRHMD != nullptr) + { + ovrpColorSpace ClientColorSpace = (ovrpColorSpace)ColorSpace; +#if PLATFORM_ANDROID + if (ClientColorSpace == ovrpColorSpace_Unknown) + { + ClientColorSpace = ovrpColorSpace_Quest; + } +#endif + FOculusXRHMDModule::GetPluginWrapper().SetClientColorDesc(ClientColorSpace); + } +#endif +} + +void UOculusXRFunctionLibrary::SetLocalDimmingOn(bool LocalDimmingOn) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusHMD = GetOculusXRHMD(); + if (OculusHMD != nullptr) + { + UE_LOG(LogHMD, Log, TEXT("SetLocalDimmingOn %d"), LocalDimmingOn); + FOculusXRHMDModule::GetPluginWrapper().SetLocalDimming(LocalDimmingOn); + } +#endif +} + +bool UOculusXRFunctionLibrary::IsPassthroughSupported() { +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusHMD = GetOculusXRHMD(); + if (OculusHMD != nullptr) + { + ovrpInsightPassthroughCapabilityFlags capabilities; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPassthroughCapabilityFlags(&capabilities))) { + return (capabilities & ovrpInsightPassthroughCapabilityFlags::ovrpInsightPassthroughCapabilityFlags_Passthrough) + == ovrpInsightPassthroughCapabilityFlags::ovrpInsightPassthroughCapabilityFlags_Passthrough; + } + + return false; + } +#endif + return false; +} + +bool UOculusXRFunctionLibrary::IsColorPassthroughSupported() { +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OculusXRHMD::FOculusXRHMD* OculusHMD = GetOculusXRHMD(); + if (OculusHMD != nullptr) + { + ovrpInsightPassthroughCapabilityFlags capabilities; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPassthroughCapabilityFlags(&capabilities))) { + return (capabilities & ovrpInsightPassthroughCapabilityFlags::ovrpInsightPassthroughCapabilityFlags_Color) + == ovrpInsightPassthroughCapabilityFlags::ovrpInsightPassthroughCapabilityFlags_Color; + } + + return false; + } +#endif + return false; +} + +#undef LOCTEXT_NAMESPACE \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0c8cf7878e966d99f0cb9959da519bace0ad87cb --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD.cpp @@ -0,0 +1,4195 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD.h" +#include "OculusXRHMDPrivateRHI.h" + +#include "EngineAnalytics.h" +#include "Interfaces/IAnalyticsProvider.h" +#include "AnalyticsEventAttribute.h" +#include "Slate/SceneViewport.h" +#include "PostProcess/PostProcessHMD.h" +#include "PostProcess/SceneRenderTargets.h" +#include "HardwareInfo.h" +#include "ScreenRendering.h" +#include "GameFramework/PlayerController.h" +#include "Math/UnrealMathUtility.h" +#include "Math/TranslationMatrix.h" +#include "Widgets/SViewport.h" +#include "Layout/WidgetPath.h" +#include "Framework/Application/SlateApplication.h" +#include "Engine/Canvas.h" +#include "Engine/GameEngine.h" +#include "Misc/CoreDelegates.h" +#include "GameFramework/WorldSettings.h" +#include "Engine/StaticMesh.h" +#include "Engine/StaticMeshActor.h" +#include "Components/InstancedStaticMeshComponent.h" +#include "Misc/EngineVersion.h" +#include "ClearQuad.h" +#include "DynamicResolutionState.h" +#include "DynamicResolutionProxy.h" +#include "OculusXRHMDRuntimeSettings.h" +#include "OculusXRDelegates.h" + +#if PLATFORM_ANDROID +#include "Android/AndroidJNI.h" +#include "Android/AndroidApplication.h" +#include "HAL/IConsoleManager.h" +#include "AndroidPermissionFunctionLibrary.h" +#include "AndroidPermissionCallbackProxy.h" +#endif +#include "OculusShaders.h" +#include "PipelineStateCache.h" + +#include "IOculusXRMRModule.h" + +#if WITH_EDITOR +#include "Editor/UnrealEd/Classes/Editor/EditorEngine.h" +#endif + +#if !UE_BUILD_SHIPPING +#include "Debug/DebugDrawService.h" +#endif + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + +static TAutoConsoleVariable<int32> CVarOculusEnableSubsampledLayout( + TEXT("r.Mobile.Oculus.EnableSubsampled"), + 0, + TEXT("0: Disable subsampled layout (Default)\n") + TEXT("1: Enable subsampled layout on supported platforms\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +static TAutoConsoleVariable<int32> CVarOculusEnableLowLatencyVRS( + TEXT("r.Mobile.Oculus.EnableLowLatencyVRS"), + 0, + TEXT("0: Disable late update of VRS textures (Default)\n") + TEXT("1: Enable late update of VRS textures\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +static TAutoConsoleVariable<int32> CVarOculusForceSymmetric( + TEXT("r.Mobile.Oculus.ForceSymmetric"), + 0, + TEXT("0: Use standard runtime-provided projection matrices (Default)\n") + TEXT("1: Render both eyes with a symmetric projection, union of both FOVs (and corresponding higher rendertarget size to maintain PD)\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +// AppSpaceWarp +static TAutoConsoleVariable<int32> CVarOculusEnableSpaceWarpUser( + TEXT("r.Mobile.Oculus.SpaceWarp.Enable"), + 0, + TEXT("0 Disable spacewarp at runtime.\n") + TEXT("1 Enable spacewarp at runtime.\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +static TAutoConsoleVariable<int32> CVarOculusEnableSpaceWarpInternal( + TEXT("r.Mobile.Oculus.SpaceWarp.EnableInternal"), + 0, + TEXT("0 Disable spacewarp, for internal engine checking, don't modify.\n") + TEXT("1 Enable spacewarp, for internal enegine checking, don't modify.\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +// Foveated Rendering +static TAutoConsoleVariable<int32> CVarOculusFoveatedRenderingMethod( + TEXT("r.Mobile.Oculus.FoveatedRendering.Method"), + -1, + TEXT("0 Fixed Foveated Rendering.\n") + TEXT("1 Eye-Tracked Foveated Rendering.\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +static TAutoConsoleVariable<int32> CVarOculusFoveatedRenderingLevel( + TEXT("r.Mobile.Oculus.FoveatedRendering.Level"), + -1, + TEXT("0 Off.\n") + TEXT("1 Low.\n") + TEXT("2 Medium.\n") + TEXT("3 High.\n") + TEXT("4 High Top.\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +static TAutoConsoleVariable<int32> CVarOculusDynamicFoveatedRendering( + TEXT("r.Mobile.Oculus.FoveatedRendering.Dynamic"), + -1, + TEXT("0 Disable Dynamic Foveated Rendering at runtime.\n") + TEXT("1 Enable Dynamic Foveated Rendering at runtime.\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +#define OCULUS_PAUSED_IDLE_FPS 10 + +namespace OculusXRHMD +{ + +#if !UE_BUILD_SHIPPING + static void __cdecl OvrpLogCallback(ovrpLogLevel level, const char* message) + { + FString tbuf = ANSI_TO_TCHAR(message); + const TCHAR* levelStr = TEXT(""); + switch (level) + { + case ovrpLogLevel_Debug: levelStr = TEXT(" Debug:"); break; + case ovrpLogLevel_Info: levelStr = TEXT(" Info:"); break; + case ovrpLogLevel_Error: levelStr = TEXT(" Error:"); break; + } + + GLog->Logf(TEXT("OCULUS:%s %s"), levelStr, *tbuf); + } +#endif // !UE_BUILD_SHIPPING + + //------------------------------------------------------------------------------------------------- + // FOculusXRHMD + //------------------------------------------------------------------------------------------------- + + const FName FOculusXRHMD::OculusSystemName(TEXT("OculusXRHMD")); + + FName FOculusXRHMD::GetSystemName() const + { + return OculusSystemName; + } + int32 FOculusXRHMD::GetXRSystemFlags() const + { + return EXRSystemFlags::IsHeadMounted; + } + + + FString FOculusXRHMD::GetVersionString() const + { + const char* Version; + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetVersion2(&Version))) + { + Version = "Unknown"; + } + + return FString::Printf(TEXT("OVRPlugin: %s"), UTF8_TO_TCHAR(Version)); + } + + + bool FOculusXRHMD::DoesSupportPositionalTracking() const + { + ovrpBool trackingPositionSupported; + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetTrackingPositionSupported2(&trackingPositionSupported)) && trackingPositionSupported; + } + + + bool FOculusXRHMD::HasValidTrackingPosition() + { + ovrpBool nodePositionTracked; + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePositionTracked2(ovrpNode_Head, &nodePositionTracked)) && nodePositionTracked; + } + + + struct TrackedDevice + { + ovrpNode Node; + EXRTrackedDeviceType Type; + }; + + static TrackedDevice TrackedDevices[] = + { + { ovrpNode_Head, EXRTrackedDeviceType::HeadMountedDisplay }, + { ovrpNode_HandLeft, EXRTrackedDeviceType::Controller }, + { ovrpNode_HandRight, EXRTrackedDeviceType::Controller }, + { ovrpNode_TrackerZero, EXRTrackedDeviceType::TrackingReference }, + { ovrpNode_TrackerOne, EXRTrackedDeviceType::TrackingReference }, + { ovrpNode_TrackerTwo, EXRTrackedDeviceType::TrackingReference }, + { ovrpNode_TrackerThree, EXRTrackedDeviceType::TrackingReference }, + { ovrpNode_DeviceObjectZero, EXRTrackedDeviceType::Other }, + }; + + static uint32 TrackedDeviceCount = sizeof(TrackedDevices) / sizeof(TrackedDevices[0]); + + bool FOculusXRHMD::EnumerateTrackedDevices(TArray<int32>& OutDevices, EXRTrackedDeviceType Type) + { + CheckInGameThread(); + + for (uint32 TrackedDeviceId = 0; TrackedDeviceId < TrackedDeviceCount; TrackedDeviceId++) + { + if (Type == EXRTrackedDeviceType::Any || Type == TrackedDevices[TrackedDeviceId].Type) + { + ovrpBool nodePresent; + + ovrpNode Node = TrackedDevices[TrackedDeviceId].Node; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePresent2(Node, &nodePresent)) && nodePresent) + { + const int32 ExternalDeviceId = OculusXRHMD::ToExternalDeviceId(Node); + OutDevices.Add(ExternalDeviceId); + } + } + } + + return true; + } + + void FOculusXRHMD::UpdateRTPoses() + { + CheckInRenderThread(); + FGameFrame* CurrentFrame = GetFrame_RenderThread(); + if (CurrentFrame) + { + if (!CurrentFrame->Flags.bRTLateUpdateDone) + { + FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, CurrentFrame->FrameNumber, 0.0); + CurrentFrame->Flags.bRTLateUpdateDone = true; + } + + } + // else, Frame_RenderThread has already been reset/rendered (or not created yet). + // This can happen when DoEnableStereo() is called, as SetViewportSize (which it calls) enques a render + // immediately - meaning two render frames were enqueued in the span of one game tick. + } + + bool FOculusXRHMD::GetCurrentPose(int32 InDeviceId, FQuat& OutOrientation, FVector& OutPosition) + { + OutOrientation = FQuat::Identity; + OutPosition = FVector::ZeroVector; + + if ((size_t) InDeviceId >= TrackedDeviceCount) + { + return false; + } + + ovrpNode Node = OculusXRHMD::ToOvrpNode(InDeviceId); + const FSettings* CurrentSettings; + FGameFrame* CurrentFrame; + + if (InRenderThread()) + { + CurrentSettings = GetSettings_RenderThread(); + CurrentFrame = GetFrame_RenderThread(); + UpdateRTPoses(); + } + else if(InGameThread()) + { + CurrentSettings = GetSettings(); + CurrentFrame = NextFrameToRender.Get(); + } + else + { + return false; + } + + if (!CurrentSettings || !CurrentFrame) + { + return false; + } + + ovrpPoseStatef PoseState; + FPose Pose; + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame->FrameNumber, Node, &PoseState)) || + !ConvertPose_Internal(PoseState.Pose, Pose, CurrentSettings, CurrentFrame->WorldToMetersScale)) + { + return false; + } + + OutPosition = Pose.Position; + OutOrientation = Pose.Orientation; + return true; + } + + + bool FOculusXRHMD::GetRelativeEyePose(int32 InDeviceId, int32 ViewIndex, FQuat& OutOrientation, FVector& OutPosition) + { + OutOrientation = FQuat::Identity; + OutPosition = FVector::ZeroVector; + + if (InDeviceId != HMDDeviceId) + { + return false; + } + + ovrpNode Node; + + switch (ViewIndex) + { + case EStereoscopicEye::eSSE_LEFT_EYE: + Node = ovrpNode_EyeLeft; + break; + case EStereoscopicEye::eSSE_RIGHT_EYE: + Node = ovrpNode_EyeRight; + break; + case EStereoscopicEye::eSSE_MONOSCOPIC: + Node = ovrpNode_EyeCenter; + break; + default: + return false; + } + + const FSettings* CurrentSettings; + FGameFrame* CurrentFrame; + + if (InRenderThread()) + { + CurrentSettings = GetSettings_RenderThread(); + CurrentFrame = GetFrame_RenderThread(); + UpdateRTPoses(); + } + else if(InGameThread()) + { + CurrentSettings = GetSettings(); + CurrentFrame = NextFrameToRender.Get(); + } + else + { + return false; + } + + if (!CurrentSettings || !CurrentFrame) + { + return false; + } + + ovrpPoseStatef HmdPoseState, EyePoseState; + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame->FrameNumber, ovrpNode_Head, &HmdPoseState)) || + OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame->FrameNumber, Node, &EyePoseState))) + { + return false; + } + + FPose HmdPose, EyePose; + HmdPose.Orientation = ToFQuat(HmdPoseState.Pose.Orientation); + HmdPose.Position = ToFVector(HmdPoseState.Pose.Position) * CurrentFrame->WorldToMetersScale; + EyePose.Orientation = ToFQuat(EyePoseState.Pose.Orientation); + EyePose.Position = ToFVector(EyePoseState.Pose.Position) * CurrentFrame->WorldToMetersScale; + + FQuat HmdOrientationInv = HmdPose.Orientation.Inverse(); + OutOrientation = HmdOrientationInv * EyePose.Orientation; + OutOrientation.Normalize(); + OutPosition = HmdOrientationInv.RotateVector(EyePose.Position - HmdPose.Position); + return true; + } + + + bool FOculusXRHMD::GetTrackingSensorProperties(int32 InDeviceId, FQuat& OutOrientation, FVector& OutPosition, FXRSensorProperties& OutSensorProperties) + { + CheckInGameThread(); + + if ((size_t) InDeviceId >= TrackedDeviceCount) + { + return false; + } + + ovrpNode Node = OculusXRHMD::ToOvrpNode(InDeviceId); + ovrpPoseStatef PoseState; + FPose Pose; + ovrpFrustum2f Frustum; + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, OVRP_CURRENT_FRAMEINDEX, Node, &PoseState)) || !ConvertPose(PoseState.Pose, Pose) || + OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetNodeFrustum2(Node, &Frustum))) + { + return false; + } + + OutPosition = Pose.Position; + OutOrientation = Pose.Orientation; + OutSensorProperties.LeftFOV = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.LeftTan)); + OutSensorProperties.RightFOV = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.RightTan)); + OutSensorProperties.TopFOV = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.UpTan)); + OutSensorProperties.BottomFOV = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.DownTan)); + OutSensorProperties.NearPlane = Frustum.zNear * Frame->WorldToMetersScale; + OutSensorProperties.FarPlane = Frustum.zFar * Frame->WorldToMetersScale; + OutSensorProperties.CameraDistance = 1.0f * Frame->WorldToMetersScale; + return true; + } + + + void FOculusXRHMD::SetTrackingOrigin(EHMDTrackingOrigin::Type InOrigin) + { + TrackingOrigin = InOrigin; + ovrpTrackingOrigin ovrpOrigin = ovrpTrackingOrigin_EyeLevel; + if (InOrigin == EHMDTrackingOrigin::Floor) + ovrpOrigin = ovrpTrackingOrigin_FloorLevel; + + if (InOrigin == EHMDTrackingOrigin::Stage) + ovrpOrigin = ovrpTrackingOrigin_Stage; + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + EHMDTrackingOrigin::Type lastOrigin = GetTrackingOrigin(); + FOculusXRHMDModule::GetPluginWrapper().SetTrackingOriginType2(ovrpOrigin); + OCFlags.NeedSetTrackingOrigin = false; + + if(lastOrigin != InOrigin) + Settings->BaseOffset = FVector::ZeroVector; + } + + OnTrackingOriginChanged(); + } + + + EHMDTrackingOrigin::Type FOculusXRHMD::GetTrackingOrigin() const + { + EHMDTrackingOrigin::Type rv = EHMDTrackingOrigin::Eye; + ovrpTrackingOrigin ovrpOrigin = ovrpTrackingOrigin_EyeLevel; + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetTrackingOriginType2(&ovrpOrigin))) + { + switch (ovrpOrigin) + { + case ovrpTrackingOrigin_EyeLevel: + rv = EHMDTrackingOrigin::Eye; + break; + case ovrpTrackingOrigin_FloorLevel: + rv = EHMDTrackingOrigin::Floor; + break; + case ovrpTrackingOrigin_Stage: + rv = EHMDTrackingOrigin::Stage; + break; + default: + UE_LOG(LogHMD, Error, TEXT("Unsupported ovr tracking origin type %d"), int(ovrpOrigin)); + break; + } + } + return rv; + } + + bool FOculusXRHMD::GetFloorToEyeTrackingTransform(FTransform& OutFloorToEye) const + { + float EyeHeight = 0.f; + const bool bSuccess = FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserEyeHeight2(&EyeHeight)); + OutFloorToEye = FTransform( FVector(0.f, 0.f, -ConvertFloat_M2U(EyeHeight)) ); + + return bSuccess; + } + + void FOculusXRHMD::ResetOrientationAndPosition(float yaw) + { + Recenter(RecenterOrientationAndPosition, yaw); + } + + void FOculusXRHMD::ResetOrientation(float yaw) + { + Recenter(RecenterOrientation, yaw); + } + + void FOculusXRHMD::ResetPosition() + { + Recenter(RecenterPosition, 0); + } + + void FOculusXRHMD::Recenter(FRecenterTypes RecenterType, float Yaw) + { + CheckInGameThread(); + + if (NextFrameToRender) + { + const bool floorLevel = GetTrackingOrigin() != EHMDTrackingOrigin::Eye; + ovrpPoseStatef poseState; + FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, NextFrameToRender->FrameNumber, 0.0); + FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, NextFrameToRender->FrameNumber, ovrpNode_Head, &poseState); + + if (RecenterType & RecenterPosition) + { + Settings->BaseOffset = ToFVector(poseState.Pose.Position); + if (floorLevel) + Settings->BaseOffset.Z = 0; + } + + if (RecenterType & RecenterOrientation) + { + Settings->BaseOrientation = FRotator(0, FRotator(ToFQuat(poseState.Pose.Orientation)).Yaw - Yaw, 0).Quaternion(); + } + } + } + + void FOculusXRHMD::SetBaseRotation(const FRotator& BaseRot) + { + SetBaseOrientation(BaseRot.Quaternion()); + } + + + FRotator FOculusXRHMD::GetBaseRotation() const + { + return GetBaseOrientation().Rotator(); + } + + + void FOculusXRHMD::SetBaseOrientation(const FQuat& BaseOrient) + { + CheckInGameThread(); + + Settings->BaseOrientation = BaseOrient; + } + + + FQuat FOculusXRHMD::GetBaseOrientation() const + { + CheckInGameThread(); + + return Settings->BaseOrientation; + } + + + bool FOculusXRHMD::IsHeadTrackingEnforced() const + { + if (IsInGameThread()) + { + return Settings.IsValid() && Settings->Flags.bHeadTrackingEnforced; + } + else + { + CheckInRenderThread(); + return Settings_RenderThread.IsValid() && Settings_RenderThread->Flags.bHeadTrackingEnforced; + } + } + + void FOculusXRHMD::SetHeadTrackingEnforced(bool bEnabled) + { + CheckInGameThread(); + check(Settings.IsValid()); + + const bool bOldValue = Settings->Flags.bHeadTrackingEnforced; + Settings->Flags.bHeadTrackingEnforced = bEnabled; + + if (!bEnabled) + { + ResetControlRotation(); + } + else if (!bOldValue) + { + InitDevice(); + } + } + + bool FOculusXRHMD::IsHeadTrackingAllowed() const + { + CheckInGameThread(); + + if (!FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + return false; + } + + return FHeadMountedDisplayBase::IsHeadTrackingAllowed(); + } + + + void FOculusXRHMD::OnBeginPlay(FWorldContext& InWorldContext) + { + CheckInGameThread(); + + CachedViewportWidget.Reset(); + CachedWindow.Reset(); + +#if WITH_EDITOR + // @TODO: add more values here. + // This call make sense when 'Play' is used from the Editor; + if (GIsEditor && !GEnableVREditorHacks) + { + Settings->BaseOrientation = FQuat::Identity; + Settings->BaseOffset = FVector::ZeroVector; + Settings->ColorScale = ovrpVector4f{ 1,1,1,1 }; + Settings->ColorOffset = ovrpVector4f{ 0,0,0,0 }; + + //Settings->WorldToMetersScale = InWorldContext.World()->GetWorldSettings()->WorldToMeters; + //Settings->Flags.bWorldToMetersOverride = false; + InitDevice(); + + FApp::SetUseVRFocus(true); + FApp::SetHasVRFocus(true); + OnStartGameFrame(InWorldContext); + } +#endif + } + + + void FOculusXRHMD::OnEndPlay(FWorldContext& InWorldContext) + { + CheckInGameThread(); + +#if WITH_EDITOR + if (GIsEditor && !GEnableVREditorHacks) + { + // @todo vreditor: If we add support for starting PIE while in VR Editor, we don't want to kill stereo mode when exiting PIE + if (Splash->IsShown()) + { + Splash->HideLoadingScreen(); // This will only request hiding the screen + Splash->UpdateLoadingScreen_GameThread(); // Update is needed to complete removing the loading screen + } + EnableStereo(false); + ReleaseDevice(); + + FApp::SetUseVRFocus(false); + FApp::SetHasVRFocus(false); + } +#endif + } + + DECLARE_STATS_GROUP(TEXT("Oculus System Metrics"), STATGROUP_OculusSystemMetrics, STATCAT_Advanced); + + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("App CPU Time (ms)"), STAT_OculusSystem_AppCpuTime, STATGROUP_OculusSystemMetrics, ); + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("App GPU Time (ms)"), STAT_OculusSystem_AppGpuTime, STATGROUP_OculusSystemMetrics, ); + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("Compositor CPU Time (ms)"), STAT_OculusSystem_ComCpuTime, STATGROUP_OculusSystemMetrics, ); + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("Compositor GPU Time (ms)"), STAT_OculusSystem_ComGpuTime, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("Compositor Dropped Frames"), STAT_OculusSystem_DroppedFrames, STATGROUP_OculusSystemMetrics, ); + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("System GPU Util %"), STAT_OculusSystem_GpuUtil, STATGROUP_OculusSystemMetrics, ); + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("System CPU Util Avg %"), STAT_OculusSystem_CpuUtilAvg, STATGROUP_OculusSystemMetrics, ); + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("System CPU Util Worst %"), STAT_OculusSystem_CpuUtilWorst, STATGROUP_OculusSystemMetrics, ); + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("CPU Clock Freq (MHz)"), STAT_OculusSystem_CpuFreq, STATGROUP_OculusSystemMetrics, ); + DECLARE_FLOAT_COUNTER_STAT_EXTERN(TEXT("GPU Clock Freq (MHz)"), STAT_OculusSystem_GpuFreq, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Clock Level"), STAT_OculusSystem_CpuClockLvl, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("GPU Clock Level"), STAT_OculusSystem_GpuClockLvl, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("SpaceWarp Mode"), STAT_OculusSystem_ComSpaceWarpMode, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core0 Util %"), STAT_OculusSystem_CpuCore0Util, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core1 Util %"), STAT_OculusSystem_CpuCore1Util, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core2 Util %"), STAT_OculusSystem_CpuCore2Util, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core3 Util %"), STAT_OculusSystem_CpuCore3Util, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core4 Util %"), STAT_OculusSystem_CpuCore4Util, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core5 Util %"), STAT_OculusSystem_CpuCore5Util, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core6 Util %"), STAT_OculusSystem_CpuCore6Util, STATGROUP_OculusSystemMetrics, ); + DECLARE_DWORD_COUNTER_STAT_EXTERN(TEXT("CPU Core7 Util %"), STAT_OculusSystem_CpuCore7Util, STATGROUP_OculusSystemMetrics, ); + + DEFINE_STAT(STAT_OculusSystem_AppCpuTime); + DEFINE_STAT(STAT_OculusSystem_AppGpuTime); + DEFINE_STAT(STAT_OculusSystem_ComCpuTime); + DEFINE_STAT(STAT_OculusSystem_ComGpuTime); + DEFINE_STAT(STAT_OculusSystem_DroppedFrames); + DEFINE_STAT(STAT_OculusSystem_GpuUtil); + DEFINE_STAT(STAT_OculusSystem_CpuUtilAvg); + DEFINE_STAT(STAT_OculusSystem_CpuUtilWorst); + DEFINE_STAT(STAT_OculusSystem_CpuFreq); + DEFINE_STAT(STAT_OculusSystem_GpuFreq); + DEFINE_STAT(STAT_OculusSystem_CpuClockLvl); + DEFINE_STAT(STAT_OculusSystem_GpuClockLvl); + DEFINE_STAT(STAT_OculusSystem_ComSpaceWarpMode); + DEFINE_STAT(STAT_OculusSystem_CpuCore0Util); + DEFINE_STAT(STAT_OculusSystem_CpuCore1Util); + DEFINE_STAT(STAT_OculusSystem_CpuCore2Util); + DEFINE_STAT(STAT_OculusSystem_CpuCore3Util); + DEFINE_STAT(STAT_OculusSystem_CpuCore4Util); + DEFINE_STAT(STAT_OculusSystem_CpuCore5Util); + DEFINE_STAT(STAT_OculusSystem_CpuCore6Util); + DEFINE_STAT(STAT_OculusSystem_CpuCore7Util); + + void UpdateOculusSystemMetricsStats() + { + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() == ovrpBool_False) + { + return; + } + + ovrpBool bIsSupported; + float valueFloat = 0; + int valueInt = 0; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_App_CpuTime_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_App_CpuTime_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_AppCpuTime, valueFloat * 1000); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_App_GpuTime_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_App_GpuTime_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_AppGpuTime, valueFloat * 1000); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Compositor_CpuTime_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Compositor_CpuTime_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_ComCpuTime, valueFloat * 1000); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Compositor_GpuTime_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Compositor_GpuTime_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_ComGpuTime, valueFloat * 1000); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Compositor_DroppedFrameCount_Int, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsInt(ovrpPerfMetrics_Compositor_DroppedFrameCount_Int, &valueInt))) + { + SET_DWORD_STAT(STAT_OculusSystem_DroppedFrames, valueInt); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_System_GpuUtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_System_GpuUtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_GpuUtil, valueFloat * 100); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_System_CpuUtilAveragePercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_System_CpuUtilAveragePercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuUtilAvg, valueFloat * 100); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_System_CpuUtilWorstPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_System_CpuUtilWorstPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuUtilWorst, valueFloat * 100); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuClockFrequencyInMHz_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuClockFrequencyInMHz_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuFreq, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_GpuClockFrequencyInMHz_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_GpuClockFrequencyInMHz_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_GpuFreq, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuClockLevel_Int, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsInt(ovrpPerfMetrics_Device_CpuClockLevel_Int, &valueInt))) + { + SET_DWORD_STAT(STAT_OculusSystem_CpuClockLvl, valueInt); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_GpuClockLevel_Int, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsInt(ovrpPerfMetrics_Device_GpuClockLevel_Int, &valueInt))) + { + SET_DWORD_STAT(STAT_OculusSystem_GpuClockLvl, valueInt); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Compositor_SpaceWarp_Mode_Int, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsInt(ovrpPerfMetrics_Compositor_SpaceWarp_Mode_Int, &valueInt))) + { + SET_DWORD_STAT(STAT_OculusSystem_ComSpaceWarpMode, valueInt); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuCore0Util, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore1UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore1UtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuCore1Util, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore2UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore2UtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuCore2Util, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore3UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore3UtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuCore3Util, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore4UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore4UtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuCore4Util, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore5UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore5UtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuCore5Util, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore6UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore6UtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuCore6Util, valueFloat); + } + } + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsPerfMetricsSupported(ovrpPerfMetrics_Device_CpuCore7UtilPercentage_Float, &bIsSupported)) && bIsSupported == ovrpBool_True) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetPerfMetricsFloat(ovrpPerfMetrics_Device_CpuCore7UtilPercentage_Float, &valueFloat))) + { + SET_FLOAT_STAT(STAT_OculusSystem_CpuCore7Util, valueFloat); + } + } + } + + bool FOculusXRHMD::OnStartGameFrame(FWorldContext& InWorldContext) + { + CheckInGameThread(); + + if (IsEngineExitRequested()) + { + return false; + } + + UpdateOculusSystemMetricsStats(); + + RefreshTrackingToWorldTransform(InWorldContext); + + // check if HMD is marked as invalid and needs to be killed. + ovrpBool appShouldRecreateDistortionWindow; + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppShouldRecreateDistortionWindow2(&appShouldRecreateDistortionWindow)) && + appShouldRecreateDistortionWindow) + { + DoEnableStereo(false); + ReleaseDevice(); + + if (!OCFlags.DisplayLostDetected) + { + FCoreDelegates::VRHeadsetLost.Broadcast(); + OCFlags.DisplayLostDetected = true; + } + + Flags.bNeedEnableStereo = true; + } +#if PLATFORM_ANDROID + Flags.bNeedEnableStereo = true; // !!! +#endif + + check(Settings.IsValid()); + if (!Settings->IsStereoEnabled()) + { + FApp::SetUseVRFocus(false); + FApp::SetHasVRFocus(false); + } + +#if OCULUS_STRESS_TESTS_ENABLED + FStressTester::TickCPU_GameThread(this); +#endif + + if (bShutdownRequestQueued) + { + bShutdownRequestQueued = false; + DoSessionShutdown(); + } + + if (!InWorldContext.World() || (!(GEnableVREditorHacks && InWorldContext.WorldType == EWorldType::Editor) && !InWorldContext.World()->IsGameWorld())) // @todo vreditor: (Also see OnEndGameFrame()) Kind of a hack here so we can use VR in editor viewports. We need to consider when running GameWorld viewports inside the editor with VR. + { + // ignore all non-game worlds + return false; + } + + bool bStereoEnabled = Settings->Flags.bStereoEnabled; + bool bStereoDesired = bStereoEnabled; + + if (Flags.bNeedEnableStereo) + { + bStereoDesired = true; + } + + if (bStereoDesired && (Flags.bNeedDisableStereo || !Settings->Flags.bHMDEnabled)) + { + bStereoDesired = false; + } + + bool bStereoDesiredAndIsConnected = bStereoDesired; + + if (bStereoDesired && !(bStereoEnabled ? IsHMDActive() : IsHMDEnabled())) + { + bStereoDesiredAndIsConnected = false; + } + + Flags.bNeedEnableStereo = false; + Flags.bNeedDisableStereo = false; + + if (bStereoEnabled != bStereoDesiredAndIsConnected) + { + bStereoEnabled = DoEnableStereo(bStereoDesiredAndIsConnected); + } + + // Keep trying to enable stereo until we succeed + Flags.bNeedEnableStereo = bStereoDesired && !bStereoEnabled; + + if (!Settings->IsStereoEnabled() && !Settings->Flags.bHeadTrackingEnforced) + { + return false; + } + + if (Flags.bApplySystemOverridesOnStereo) + { + ApplySystemOverridesOnStereo(); + Flags.bApplySystemOverridesOnStereo = false; + } + + CachedWorldToMetersScale = InWorldContext.World()->GetWorldSettings()->WorldToMeters; + + // this should have already happened in FOculusXRInput, so this is usually a no-op. + StartGameFrame_GameThread(); + + bool retval = true; + + UpdateHMDEvents(); + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + if (OCFlags.DisplayLostDetected) + { + FCoreDelegates::VRHeadsetReconnected.Broadcast(); + OCFlags.DisplayLostDetected = false; + } + + if (OCFlags.NeedSetTrackingOrigin) + { + SetTrackingOrigin(TrackingOrigin); + } + + ovrpBool bAppHasVRFocus = ovrpBool_False; + FOculusXRHMDModule::GetPluginWrapper().GetAppHasVrFocus2(&bAppHasVRFocus); + + FApp::SetUseVRFocus(true); + FApp::SetHasVRFocus(bAppHasVRFocus != ovrpBool_False); + + // Do not pause if Editor is running (otherwise it will become very laggy) + if (!GIsEditor) + { + if (!bAppHasVRFocus) + { + // not visible, + if (!Settings->Flags.bPauseRendering) + { + UE_LOG(LogHMD, Log, TEXT("The app went out of VR focus, seizing rendering...")); + } + } + else if (Settings->Flags.bPauseRendering) + { + UE_LOG(LogHMD, Log, TEXT("The app got VR focus, restoring rendering...")); + } + if (OCFlags.NeedSetFocusToGameViewport) + { + if (bAppHasVRFocus) + { + UE_LOG(LogHMD, Log, TEXT("Setting user focus to game viewport since session status is visible...")); + FSlateApplication::Get().SetAllUserFocusToGameViewport(); + OCFlags.NeedSetFocusToGameViewport = false; + } + } + + bool bPrevPause = Settings->Flags.bPauseRendering; + Settings->Flags.bPauseRendering = !bAppHasVRFocus; + + if (Settings->Flags.bPauseRendering && (GEngine->GetMaxFPS() != OCULUS_PAUSED_IDLE_FPS)) + { + GEngine->SetMaxFPS(OCULUS_PAUSED_IDLE_FPS); + } + + if (bPrevPause != Settings->Flags.bPauseRendering) + { + APlayerController* const PC = GEngine->GetFirstLocalPlayerController(InWorldContext.World()); + if (Settings->Flags.bPauseRendering) + { + // focus is lost + GEngine->SetMaxFPS(OCULUS_PAUSED_IDLE_FPS); + + if (!FCoreDelegates::ApplicationWillEnterBackgroundDelegate.IsBound()) + { + OCFlags.AppIsPaused = false; + // default action: set pause if not already paused + if (PC && !PC->IsPaused()) + { + PC->SetPause(true); + OCFlags.AppIsPaused = true; + } + } + else + { + FCoreDelegates::ApplicationWillEnterBackgroundDelegate.Broadcast(); + } + } + else + { + // focus is gained + GEngine->SetMaxFPS(0); + + if (!FCoreDelegates::ApplicationHasEnteredForegroundDelegate.IsBound()) + { + // default action: unpause if was paused by the plugin + if (PC && OCFlags.AppIsPaused) + { + PC->SetPause(false); + } + OCFlags.AppIsPaused = false; + } + else + { + FCoreDelegates::ApplicationHasEnteredForegroundDelegate.Broadcast(); + } + } + } + } + + ovrpBool AppShouldQuit; + ovrpBool AppShouldRecenter; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppShouldQuit2(&AppShouldQuit)) && AppShouldQuit || OCFlags.EnforceExit) + { + FPlatformMisc::LowLevelOutputDebugString(TEXT("OculusXRHMD plugin requested exit (ShouldQuit == 1)\n")); +#if WITH_EDITOR + if (GIsEditor) + { + FSceneViewport* SceneVP = FindSceneViewport(); + if (SceneVP && SceneVP->IsStereoRenderingAllowed()) + { + TSharedPtr<SWindow> Window = SceneVP->FindWindow(); + Window->RequestDestroyWindow(); + } + } + else +#endif//WITH_EDITOR + { + // ApplicationWillTerminateDelegate will fire from inside of the RequestExit + FPlatformMisc::RequestExit(false); + } + OCFlags.EnforceExit = false; + retval = false; + } + else if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppShouldRecenter2(&AppShouldRecenter)) && AppShouldRecenter) + { + FPlatformMisc::LowLevelOutputDebugString(TEXT("OculusXRHMD plugin was requested to recenter\n")); + if (FCoreDelegates::VRHeadsetRecenter.IsBound()) + { + FCoreDelegates::VRHeadsetRecenter.Broadcast(); + } + else + { + ResetOrientationAndPosition(); + } + + // Call FOculusXRHMDModule::GetPluginWrapper().RecenterTrackingOrigin2 to clear AppShouldRecenter flag + FOculusXRHMDModule::GetPluginWrapper().RecenterTrackingOrigin2(ovrpRecenterFlag_IgnoreAll); + } + + UpdateHMDWornState(); + } + +#if OCULUS_MR_SUPPORTED_PLATFORMS + if (FOculusXRHMDModule::GetPluginWrapper().GetMixedRealityInitialized()) + { + FOculusXRHMDModule::GetPluginWrapper().UpdateExternalCamera(); + FOculusXRHMDModule::GetPluginWrapper().UpdateCameraDevices(); + } +#endif + + if (IsEngineExitRequested()) + { + PreShutdown(); + } + + return retval; + } + + void FOculusXRHMD::DoSessionShutdown() + { + // Release resources + ExecuteOnRenderThread([this]() + { + ExecuteOnRHIThread([this]() + { + for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++) + { + Layers_RenderThread[LayerIndex]->ReleaseResources_RHIThread(); + } + + for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++) + { + Layers_RHIThread[LayerIndex]->ReleaseResources_RHIThread(); + } + + if (Splash.IsValid()) + { + Splash->ReleaseResources_RHIThread(); + } + + if (CustomPresent) + { + CustomPresent->ReleaseResources_RHIThread(); + } + + Settings_RHIThread.Reset(); + Frame_RHIThread.Reset(); + Layers_RHIThread.Reset(); + }); + + Settings_RenderThread.Reset(); + Frame_RenderThread.Reset(); + Layers_RenderThread.Reset(); + EyeLayer_RenderThread.Reset(); + + DeferredDeletion.HandleLayerDeferredDeletionQueue_RenderThread(true); + }); + + Frame.Reset(); + NextFrameToRender.Reset(); + LastFrameToRender.Reset(); + +#if !UE_BUILD_SHIPPING + UDebugDrawService::Unregister(DrawDebugDelegateHandle); +#endif + + // The Editor may release VR focus in OnEndPlay + if (!GIsEditor) + { + FApp::SetUseVRFocus(false); + FApp::SetHasVRFocus(false); + } + + ShutdownInsightPassthrough(); + ShutdownSession(); + + } + + bool FOculusXRHMD::OnEndGameFrame(FWorldContext& InWorldContext) + { + CheckInGameThread(); + + FGameFrame* const CurrentGameFrame = Frame.Get(); + + if (CurrentGameFrame) + { + // don't use the cached value, as it could be affected by the player's position, so we update it here at the latest point in the game frame + CurrentGameFrame->TrackingToWorld = ComputeTrackingToWorldTransform(InWorldContext); + CurrentGameFrame->LastTrackingToWorld = LastTrackingToWorld; + LastTrackingToWorld = CurrentGameFrame->TrackingToWorld; + } + else + { + return false; + } + + if ( !InWorldContext.World() || (!(GEnableVREditorHacks && InWorldContext.WorldType == EWorldType::Editor) && !InWorldContext.World()->IsGameWorld()) ) + { + // ignore all non-game worlds + return false; + } + + FinishGameFrame_GameThread(); + + return true; + } + + FVector2D FOculusXRHMD::GetPlayAreaBounds(EHMDTrackingOrigin::Type Origin) const + { + ovrpVector3f Dimensions; + + if (Origin == EHMDTrackingOrigin::Stage && + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetBoundaryDimensions2(ovrpBoundary_PlayArea, &Dimensions))) + { + Dimensions.z *= -1.0; + FVector Bounds = ConvertVector_M2U(Dimensions); + return FVector2D(Bounds.X, Bounds.Z); + } + return FVector2D::ZeroVector; + } + + + bool FOculusXRHMD::IsHMDEnabled() const + { + CheckInGameThread(); + + return (Settings->Flags.bHMDEnabled); + } + + + EHMDWornState::Type FOculusXRHMD::GetHMDWornState() + { + ovrpBool userPresent; + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserPresent2(&userPresent)) && userPresent) + { + return EHMDWornState::Worn; + } + else + { + return EHMDWornState::NotWorn; + } + } + + + void FOculusXRHMD::EnableHMD(bool enable) + { + CheckInGameThread(); + + Settings->Flags.bHMDEnabled = enable; + if (!Settings->Flags.bHMDEnabled) + { + EnableStereo(false); + } + } + + bool FOculusXRHMD::GetHMDMonitorInfo(MonitorInfo& MonitorDesc) + { + CheckInGameThread(); + + MonitorDesc.MonitorName = FString("Oculus Window"); + MonitorDesc.MonitorId = 0; + MonitorDesc.DesktopX = MonitorDesc.DesktopY = 0; + MonitorDesc.ResolutionX = MonitorDesc.ResolutionY = 0; + MonitorDesc.WindowSizeX = MonitorDesc.WindowSizeY = 0; + + if (Settings.IsValid()) + { + MonitorDesc.ResolutionX = MonitorDesc.WindowSizeX = Settings->RenderTargetSize.X; + MonitorDesc.ResolutionY = MonitorDesc.WindowSizeY = Settings->RenderTargetSize.Y; + } + + return true; + } + + + void FOculusXRHMD::GetFieldOfView(float& InOutHFOVInDegrees, float& InOutVFOVInDegrees) const + + { + ovrpFrustum2f Frustum; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodeFrustum2(ovrpNode_EyeCenter, &Frustum))) + { + InOutVFOVInDegrees = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.UpTan) + FMath::Atan(Frustum.Fov.DownTan)); + InOutHFOVInDegrees = FMath::RadiansToDegrees(FMath::Atan(Frustum.Fov.LeftTan) + FMath::Atan(Frustum.Fov.RightTan)); + } + } + + + void FOculusXRHMD::SetInterpupillaryDistance(float NewInterpupillaryDistance) + { + CheckInGameThread(); + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + FOculusXRHMDModule::GetPluginWrapper().SetUserIPD2(NewInterpupillaryDistance); + } + } + + + float FOculusXRHMD::GetInterpupillaryDistance() const + { + CheckInGameThread(); + + float UserIPD; + + if (!FOculusXRHMDModule::GetPluginWrapper().GetInitialized() || OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetUserIPD2(&UserIPD))) + { + return 0.0f; + } + + return UserIPD; + } + + + bool FOculusXRHMD::GetHMDDistortionEnabled(EShadingPath /* ShadingPath */) const + { + return false; + } + + + bool FOculusXRHMD::IsChromaAbCorrectionEnabled() const + { + CheckInGameThread(); + + return true; + } + + + bool FOculusXRHMD::HasHiddenAreaMesh() const + { + if (IsInRenderingThread()) + { + if (ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread()) + { + return false; + } + } + + return HiddenAreaMeshes[0].IsValid() && HiddenAreaMeshes[1].IsValid(); + } + + + bool FOculusXRHMD::HasVisibleAreaMesh() const + { + if (IsInRenderingThread()) + { + if (ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread()) + { + return false; + } + } + + return VisibleAreaMeshes[0].IsValid() && VisibleAreaMeshes[1].IsValid(); + } + + + static void DrawOcclusionMesh(FRHICommandList& RHICmdList, int32 ViewIndex, const FHMDViewMesh MeshAssets[]) + { + check(ViewIndex != INDEX_NONE); + + const uint32 MeshIndex = (ViewIndex == EStereoscopicEye::eSSE_LEFT_EYE) ? 0 : 1; + const FHMDViewMesh& Mesh = MeshAssets[MeshIndex]; + check(Mesh.IsValid()); + + RHICmdList.SetStreamSource(0, Mesh.VertexBufferRHI, 0); + RHICmdList.DrawIndexedPrimitive(Mesh.IndexBufferRHI, 0, 0, Mesh.NumVertices, 0, Mesh.NumTriangles, 1); + } + + + void FOculusXRHMD::DrawHiddenAreaMesh(FRHICommandList& RHICmdList, int32 ViewIndex) const + { + DrawOcclusionMesh(RHICmdList, ViewIndex, HiddenAreaMeshes); + } + + + void FOculusXRHMD::DrawVisibleAreaMesh(FRHICommandList& RHICmdList, int32 ViewIndex) const + { + DrawOcclusionMesh(RHICmdList, ViewIndex, VisibleAreaMeshes); + } + + float FOculusXRHMD::GetPixelDenity() const + { + if (IsInGameThread()) + { + return Settings.IsValid() ? Settings->PixelDensity : 1.0f; + } + else + { + return Settings_RenderThread.IsValid() ? Settings_RenderThread->PixelDensity : 1.0f; + } + } + + void FOculusXRHMD::SetPixelDensity(const float NewPixelDensity) + { + CheckInGameThread(); + Settings->SetPixelDensity(NewPixelDensity); + } + + FIntPoint FOculusXRHMD::GetIdealRenderTargetSize() const + { + if (IsInGameThread()) + { + return Settings.IsValid() ? Settings->RenderTargetSize : 1.0f; + } + else + { + return Settings_RenderThread.IsValid() ? Settings_RenderThread->RenderTargetSize : 1.0f; + } + } + + bool FOculusXRHMD::IsStereoEnabled() const + { + if (IsInGameThread()) + { + return Settings.IsValid() && Settings->IsStereoEnabled(); + } + else + { + return Settings_RenderThread.IsValid() && Settings_RenderThread->IsStereoEnabled(); + } + } + + + bool FOculusXRHMD::IsStereoEnabledOnNextFrame() const + { + // !!! + + return Settings.IsValid() && Settings->IsStereoEnabled(); + } + + + bool FOculusXRHMD::EnableStereo(bool bStereo) + { + CheckInGameThread(); + + return DoEnableStereo(bStereo); + } + + + void FOculusXRHMD::AdjustViewRect(int32 ViewIndex, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const + { + if (Settings.IsValid()) + { + X = Settings->EyeUnscaledRenderViewport[ViewIndex].Min.X; + Y = Settings->EyeUnscaledRenderViewport[ViewIndex].Min.Y; + SizeX = Settings->EyeUnscaledRenderViewport[ViewIndex].Size().X; + SizeY = Settings->EyeUnscaledRenderViewport[ViewIndex].Size().Y; + } + else + { + SizeX = SizeX / 2; + X += SizeX * ViewIndex; + } + } + + void FOculusXRHMD::SetFinalViewRect(FRHICommandListImmediate& RHICmdList, const int32 ViewIndex, const FIntRect& FinalViewRect) + { + CheckInRenderThread(); + + if (Settings_RenderThread.IsValid() && Settings_RenderThread->Flags.bPixelDensityAdaptive) + { + Settings_RenderThread->EyeRenderViewport[ViewIndex] = FinalViewRect; + } + + // Called after RHIThread has already started. Need to update Settings_RHIThread as well. + ExecuteOnRHIThread_DoNotWait([this, ViewIndex, FinalViewRect]() + { + CheckInRHIThread(); + + if (Settings_RHIThread.IsValid() && Settings_RHIThread->Flags.bPixelDensityAdaptive) + { + Settings_RHIThread->EyeRenderViewport[ViewIndex] = FinalViewRect; + } + }); + } + + void FOculusXRHMD::CalculateStereoViewOffset(const int32 ViewIndex, FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation) + { + // This method is called from GetProjectionData on a game thread. + if (InGameThread() && ViewIndex == EStereoscopicEye::eSSE_LEFT_EYE && NextFrameToRender.IsValid()) + { + // Inverse out GameHeadPose.Rotation since PlayerOrientation already contains head rotation. + FQuat HeadOrientation = FQuat::Identity; + FVector HeadPosition; + + GetCurrentPose(HMDDeviceId, HeadOrientation, HeadPosition); + + NextFrameToRender->PlayerOrientation = LastPlayerOrientation = ViewRotation.Quaternion() * HeadOrientation.Inverse(); + NextFrameToRender->PlayerLocation = LastPlayerLocation = ViewLocation; + } + + FHeadMountedDisplayBase::CalculateStereoViewOffset(ViewIndex, ViewRotation, WorldToMeters, ViewLocation); + } + + + FMatrix FOculusXRHMD::GetStereoProjectionMatrix(int32 ViewIndex) const + { + CheckInGameThread(); + + check(IsStereoEnabled()); + + FMatrix proj = (ViewIndex == EStereoscopicEye::eSSE_MONOSCOPIC) ? + ToFMatrix(Settings->MonoProjectionMatrix) : ToFMatrix(Settings->EyeProjectionMatrices[ViewIndex]); + + // correct far and near planes for reversed-Z projection matrix + const float WorldScale = GetWorldToMetersScale() * (1.0 / 100.0f); // physical scale is 100 UUs/meter + float InNearZ = GNearClippingPlane * WorldScale; + + proj.M[3][3] = 0.0f; + proj.M[2][3] = 1.0f; + + proj.M[2][2] = 0.0f; + proj.M[3][2] = InNearZ; + + return proj; + } + + + void FOculusXRHMD::InitCanvasFromView(FSceneView* InView, UCanvas* Canvas) + { + // This is used for placing small HUDs (with names) + // over other players (for example, in Capture Flag). + // HmdOrientation should be initialized by GetCurrentOrientation (or + // user's own value). + } + + + + void FOculusXRHMD::RenderTexture_RenderThread(class FRHICommandListImmediate& RHICmdList, class FRHITexture* BackBuffer, class FRHITexture* SrcTexture, FVector2D WindowSize) const + { + CheckInRenderThread(); + check(CustomPresent); + +#if PLATFORM_ANDROID + return; +#endif + + if (SpectatorScreenController) + { + SpectatorScreenController->RenderSpectatorScreen_RenderThread(RHICmdList, BackBuffer, SrcTexture, WindowSize); + } + } + + FVector2D FOculusXRHMD::GetEyeCenterPoint_RenderThread(int32 ViewIndex) const + { + CheckInRenderThread(); + + check(IsStereoEnabled() || IsHeadTrackingEnforced()); + + // Don't use GetStereoProjectionMatrix because it is game thread only on oculus, we also don't need the zplane adjustments for this. + const FMatrix StereoProjectionMatrix = ToFMatrix(Settings_RenderThread->EyeProjectionMatrices[ViewIndex]); + + //0,0,1 is the straight ahead point, wherever it maps to is the center of the projection plane in -1..1 coordinates. -1,-1 is bottom left. + const FVector4 ScreenCenter = StereoProjectionMatrix.TransformPosition(FVector(0.0f, 0.0f, 1.0f)); + //transform into 0-1 screen coordinates 0,0 is top left. + const FVector2D CenterPoint(0.5f + (ScreenCenter.X / 2.0f), 0.5f - (ScreenCenter.Y / 2.0f) ); + + return CenterPoint; + } + + FIntRect FOculusXRHMD::GetFullFlatEyeRect_RenderThread(FTexture2DRHIRef EyeTexture) const + { + CheckInRenderThread(); + + // Rift does this differently than other platforms, it already has an idea of what rectangle it wants to use stored. + FIntRect& EyeRect = Settings_RenderThread->EyeRenderViewport[0]; + + // But the rectangle rift specifies has corners cut off, so we will crop a little more. + if (ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread()) + { + return EyeRect; + } + else + { + static FVector2D SrcNormRectMin(0.05f, 0.0f); + static FVector2D SrcNormRectMax(0.95f, 1.0f); + const int32 SizeX = EyeRect.Max.X - EyeRect.Min.X; + const int32 SizeY = EyeRect.Max.Y - EyeRect.Min.Y; + return FIntRect(EyeRect.Min.X + SizeX * SrcNormRectMin.X, EyeRect.Min.Y + SizeY * SrcNormRectMin.Y, EyeRect.Min.X + SizeX * SrcNormRectMax.X, EyeRect.Min.Y + SizeY * SrcNormRectMax.Y); + } + } + + + void FOculusXRHMD::CopyTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture2D* SrcTexture, FIntRect SrcRect, FRHITexture2D* DstTexture, FIntRect DstRect, bool bClearBlack, bool bNoAlpha) const + { + if (bClearBlack) + { + FRHIRenderPassInfo RPInfo(DstTexture, ERenderTargetActions::DontLoad_Store); + RHICmdList.BeginRenderPass(RPInfo, TEXT("ClearToBlack")); + { + const FIntRect ClearRect(0, 0, DstTexture->GetSizeX(), DstTexture->GetSizeY()); + RHICmdList.SetViewport(ClearRect.Min.X, ClearRect.Min.Y, 0, ClearRect.Max.X, ClearRect.Max.Y, 1.0f); + DrawClearQuad(RHICmdList, FLinearColor::Black); + } + RHICmdList.EndRenderPass(); + } + + check(CustomPresent); + CustomPresent->CopyTexture_RenderThread(RHICmdList, DstTexture, SrcTexture, DstRect, SrcRect, false, bNoAlpha, true, true); + } + + + bool FOculusXRHMD::PopulateAnalyticsAttributes(TArray<FAnalyticsEventAttribute>& EventAttributes) + { + if (!FHeadMountedDisplayBase::PopulateAnalyticsAttributes(EventAttributes)) + { + return false; + } + + EventAttributes.Add(FAnalyticsEventAttribute(TEXT("HQBuffer"), (bool)Settings->Flags.bHQBuffer)); + EventAttributes.Add(FAnalyticsEventAttribute(TEXT("HQDistortion"), (bool)Settings->Flags.bHQDistortion)); + EventAttributes.Add(FAnalyticsEventAttribute(TEXT("UpdateOnRT"), (bool)Settings->Flags.bUpdateOnRT)); + + return true; + } + + + bool FOculusXRHMD::ShouldUseSeparateRenderTarget() const + { + return IsStereoEnabled(); + } + + + void FOculusXRHMD::CalculateRenderTargetSize(const FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY) + { + // TODO this should use Settings_RenderThread if !CheckInGameThread() + // This is called before StartRenderFrame_GameThread() on startup + if (!Settings->IsStereoEnabled()) + { + return; + } + + InOutSizeX = Settings->RenderTargetSize.X; + InOutSizeY = Settings->RenderTargetSize.Y; + + check(InOutSizeX != 0 && InOutSizeY != 0); + } + + void FOculusXRHMD::AllocateEyeBuffer() + { + CheckInGameThread(); + + ExecuteOnRenderThread([&]() + { + InitializeEyeLayer_RenderThread(GetImmediateCommandList_ForRenderCommand()); + + const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetSwapChain(); + if (SwapChain.IsValid()) + { + const FRHITexture2D* const SwapChainTexture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D(); + UE_LOG(LogHMD, Log, TEXT("Allocating Oculus %d x %d rendertarget swapchain"), SwapChainTexture->GetSizeX(), SwapChainTexture->GetSizeY()); + } + }); + + bNeedReAllocateViewportRenderTarget = true; + } + + bool FOculusXRHMD::NeedReAllocateViewportRenderTarget(const FViewport& Viewport) + { + CheckInGameThread(); + + return ensureMsgf(Settings.IsValid(), TEXT("Unexpected issue with Oculus settings on the GameThread. This should be valid when this is called in EnqueueBeginRenderFrame() - has the callsite changed?")) && + Settings->IsStereoEnabled() && bNeedReAllocateViewportRenderTarget; + } + + + bool FOculusXRHMD::NeedReAllocateDepthTexture(const TRefCountPtr<IPooledRenderTarget>& DepthTarget) + { + CheckInRenderThread(); + + return ensureMsgf(Settings_RenderThread.IsValid(), TEXT("Unexpected issue with Oculus settings on the RenderThread. This should be valid when this is called in AllocateCommonDepthTargets() - has the callsite changed?")) && + Settings_RenderThread->IsStereoEnabled() && bNeedReAllocateDepthTexture_RenderThread; + } + + + bool FOculusXRHMD::NeedReAllocateShadingRateTexture(const TRefCountPtr<IPooledRenderTarget>& FoveationTarget) + { + CheckInRenderThread(); + + return ensureMsgf(Settings_RenderThread.IsValid(), TEXT("Unexpected issue with Oculus settings on the RenderThread. This should be valid when this is called in AllocateFoveationTexture() - has the callsite changed?")) && + Settings_RenderThread->IsStereoEnabled() && bNeedReAllocateFoveationTexture_RenderThread; + } + +#ifdef WITH_OCULUS_BRANCH + bool FOculusXRHMD::NeedReAllocateMotionVectorTexture(const TRefCountPtr<IPooledRenderTarget>& MotionVectorTarget, const TRefCountPtr<IPooledRenderTarget>& MotionVectorDepthTarget) + { + CheckInRenderThread(); + + return ensureMsgf(Settings_RenderThread.IsValid(), TEXT("Unexpected issue with Oculus settings on the RenderThread. This should be valid when this is called in AllocateMotionVectorTexture() - has the callsite changed?")) && + Settings_RenderThread->IsStereoEnabled() && bNeedReAllocateMotionVectorTexture_RenderThread; + } +#endif // WITH_OCULUS_BRANCH + + bool FOculusXRHMD::AllocateRenderTargetTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples) + { + CheckInRenderThread(); + + check(Index == 0); + + if (LayerMap[0].IsValid()) + { + const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetSwapChain(); + if (SwapChain.IsValid()) + { + OutTargetableTexture = OutShaderResourceTexture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D(); + bNeedReAllocateViewportRenderTarget = false; + return true; + } + } + + return false; + } + + + bool FOculusXRHMD::AllocateDepthTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags FlagsIn, ETextureCreateFlags TargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples) + { + CheckInRenderThread(); + + check(Index == 0); + + if (EyeLayer_RenderThread.IsValid()) + { + const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetDepthSwapChain(); + + if (SwapChain.IsValid()) + { + FTexture2DRHIRef Texture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D(); + FIntPoint TexSize = Texture->GetSizeXY(); + + // Ensure the texture size matches the eye layer. We may get other depth allocations unrelated to the main scene render. + if (FIntPoint(SizeX, SizeY) == TexSize) + { + if (bNeedReAllocateDepthTexture_RenderThread) + { + UE_LOG(LogHMD, Log, TEXT("Allocating Oculus %d x %d depth rendertarget swapchain"), SizeX, SizeY); + bNeedReAllocateDepthTexture_RenderThread = false; + } + + OutTargetableTexture = OutShaderResourceTexture = Texture; + return true; + } + } + } + + return false; + } + + bool FOculusXRHMD::AllocateShadingRateTexture(uint32 Index, uint32 RenderSizeX, uint32 RenderSizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTexture, FIntPoint& OutTextureSize) + { + CheckInRenderThread(); + + check(Index == 0); + + if (EyeLayer_RenderThread.IsValid()) + { + const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetFoveationSwapChain(); + + if (SwapChain.IsValid()) + { + FTexture2DRHIRef Texture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D(); + FIntPoint TexSize = Texture->GetSizeXY(); + + // Only set texture and return true if we have a valid texture of compatible size + if (Texture->IsValid() && TexSize.X > 0 && TexSize.Y > 0 ) + { + if (bNeedReAllocateFoveationTexture_RenderThread) + { + UE_LOG(LogHMD, Log, TEXT("Allocating Oculus %d x %d variable resolution swapchain"), TexSize.X, TexSize.Y, Index); + bNeedReAllocateFoveationTexture_RenderThread = false; + } + + OutTexture = Texture; + OutTextureSize = TexSize; + return true; + } + } + } + + return false; + } + +#ifdef WITH_OCULUS_BRANCH + bool FOculusXRHMD::AllocateMotionVectorTexture(uint32 Index, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTexture, FIntPoint& OutTextureSize, FTexture2DRHIRef& OutDepthTexture, FIntPoint& OutDepthTextureSize) + { + CheckInRenderThread(); + + check(Index == 0); + if (EyeLayer_RenderThread.IsValid()) + { + const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetMotionVectorSwapChain(); + if (SwapChain.IsValid()) + { + FTexture2DRHIRef Texture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D(); + FIntPoint TexSize = Texture->GetSizeXY(); + + const FXRSwapChainPtr& DepthSwapChain = EyeLayer_RenderThread->GetMotionVectorDepthSwapChain(); + if (DepthSwapChain.IsValid()) + { + FTexture2DRHIRef DepthTexture = DepthSwapChain->GetTexture2DArray() ? DepthSwapChain->GetTexture2DArray() : DepthSwapChain->GetTexture2D(); + FIntPoint DepthTexSize = DepthTexture->GetSizeXY(); + + if (DepthTexture->IsValid() && DepthTexSize.X > 0 && DepthTexSize.Y > 0) + { + OutDepthTextureSize = DepthTexSize; + OutDepthTexture = DepthTexture; + } + else + { + return false; + } + } + + // Only set texture and return true if we have a valid texture of compatible size + if (Texture->IsValid() && TexSize.X > 0 && TexSize.Y > 0) + { + if (bNeedReAllocateMotionVectorTexture_RenderThread) + { + UE_LOG(LogHMD, Log, TEXT("[Mobile SpaceWarp] Allocating Oculus %d x %d motion vector swapchain"), TexSize.X, TexSize.Y, Index); + bNeedReAllocateMotionVectorTexture_RenderThread = false; + } + + OutTexture = Texture; + OutTextureSize = TexSize; + return true; + } + } + } + + return false; + } +#endif // WITH_OCULUS_BRANCH + + void FOculusXRHMD::UpdateViewportWidget(bool bUseSeparateRenderTarget, const class FViewport& Viewport, class SViewport* ViewportWidget) + { + CheckInGameThread(); + check(ViewportWidget); + + TSharedPtr<SWindow> Window = CachedWindow.Pin(); + TSharedPtr<SWidget> CurrentlyCachedWidget = CachedViewportWidget.Pin(); + TSharedRef<SWidget> Widget = ViewportWidget->AsShared(); + + if (!Window.IsValid() || Widget != CurrentlyCachedWidget) + { + Window = FSlateApplication::Get().FindWidgetWindow(Widget); + + CachedViewportWidget = Widget; + CachedWindow = Window; + } + + if (!Settings->IsStereoEnabled()) + { + // Restore AutoResizeViewport mode for the window + if (Window.IsValid()) + { + Window->SetMirrorWindow(false); + Window->SetViewportSizeDrivenByWindow(true); + } + return; + } + + if (bUseSeparateRenderTarget && Frame.IsValid()) + { + CachedWindowSize = (Window.IsValid()) ? Window->GetSizeInScreen() : Viewport.GetSizeXY(); + } + } + + + FXRRenderBridge* FOculusXRHMD::GetActiveRenderBridge_GameThread(bool bUseSeparateRenderTarget) + { + CheckInGameThread(); + + if (bUseSeparateRenderTarget && NextFrameToRender.IsValid()) + { + return CustomPresent; + } + else + { + return nullptr; + } + + } + + void FOculusXRHMD::UpdateHMDWornState() + { + const EHMDWornState::Type NewHMDWornState = GetHMDWornState(); + + if (NewHMDWornState != HMDWornState) + { + HMDWornState = NewHMDWornState; + if (HMDWornState == EHMDWornState::Worn) + { + FCoreDelegates::VRHeadsetPutOnHead.Broadcast(); + } + else if (HMDWornState == EHMDWornState::NotWorn) + { + FCoreDelegates::VRHeadsetRemovedFromHead.Broadcast(); + } + } + } + + void FOculusXRHMD::UpdateHMDEvents() + { + ovrpEventDataBuffer buf; + while (FOculusXRHMDModule::GetPluginWrapper().PollEvent(&buf) == ovrpSuccess) + { + if (buf.EventType == ovrpEventType_None) + { + break; + } + else if (buf.EventType == ovrpEventType_DisplayRefreshRateChange) + { + ovrpEventDisplayRefreshRateChange* rateChangedEvent = (ovrpEventDisplayRefreshRateChange*)&buf; + FOculusEventDelegates::OculusDisplayRefreshRateChanged.Broadcast(rateChangedEvent->FromRefreshRate, rateChangedEvent->ToRefreshRate); + } + else + { + for (auto& it : EventPollingDelegates) + { + bool HandledEvent = false; + it.ExecuteIfBound(&buf, HandledEvent); + } + } + } + } + + uint32 FOculusXRHMD::CreateLayer(const IStereoLayers::FLayerDesc& InLayerDesc) + { + CheckInGameThread(); + + uint32 LayerId = NextLayerId++; + LayerMap.Add(LayerId, MakeShareable(new FLayer(LayerId, InLayerDesc))); + return LayerId; + } + + void FOculusXRHMD::DestroyLayer(uint32 LayerId) + { + CheckInGameThread(); + FLayerPtr* LayerFound = LayerMap.Find(LayerId); + if (LayerFound) + { + (*LayerFound)->DestroyLayer(); + } + LayerMap.Remove(LayerId); + } + + + void FOculusXRHMD::SetLayerDesc(uint32 LayerId, const IStereoLayers::FLayerDesc& InLayerDesc) + { + CheckInGameThread(); + FLayerPtr* LayerFound = LayerMap.Find(LayerId); + + if (LayerFound) + { + FLayer* Layer = new FLayer(**LayerFound); + Layer->SetDesc(InLayerDesc); + *LayerFound = MakeShareable(Layer); + } + } + + + bool FOculusXRHMD::GetLayerDesc(uint32 LayerId, IStereoLayers::FLayerDesc& OutLayerDesc) + { + CheckInGameThread(); + FLayerPtr* LayerFound = LayerMap.Find(LayerId); + + if (LayerFound) + { + OutLayerDesc = (*LayerFound)->GetDesc(); + return true; + } + + return false; + } + + + void FOculusXRHMD::MarkTextureForUpdate(uint32 LayerId) + { + CheckInGameThread(); + FLayerPtr* LayerFound = LayerMap.Find(LayerId); + + if (LayerFound) + { + (*LayerFound)->MarkTextureForUpdate(); + } + } + + void FOculusXRHMD::SetSplashRotationToForward() + { + //if update splash screen is shown, update the head orientation default to recenter splash screens + FQuat HeadOrientation = FQuat::Identity; + FVector HeadPosition; + GetCurrentPose(HMDDeviceId, HeadOrientation, HeadPosition); + SplashRotation = FRotator(HeadOrientation); + SplashRotation.Pitch = 0; + SplashRotation.Roll = 0; + } + + FOculusXRSplashDesc FOculusXRHMD::GetUESplashScreenDesc() + { + FOculusXRSplashDesc Desc; + Desc.LoadedTexture = bSplashShowMovie ? SplashMovie : SplashTexture; + Desc.TransformInMeters = Desc.TransformInMeters * FTransform(SplashOffset/GetWorldToMetersScale()); + Desc.bNoAlphaChannel = true; + Desc.bIsDynamic = bSplashShowMovie; + Desc.QuadSizeInMeters *= SplashScale; + return Desc; + } + + void FOculusXRHMD::EyeTrackedFoveatedRenderingFallback() + { + FoveatedRenderingMethod = EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering; + FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::High; + bDynamicFoveatedRendering = true; + } + + void FOculusXRHMD::GetAllocatedTexture(uint32 LayerId, FTextureRHIRef &Texture, FTextureRHIRef &LeftTexture) + { + Texture = LeftTexture = nullptr; + FLayerPtr* LayerFound = nullptr; + + if (IsInGameThread()) + { + LayerFound = LayerMap.Find(LayerId); + } + else if (IsInRenderingThread()) + { + for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++) + { + if (Layers_RenderThread[LayerIndex]->GetId() == LayerId) + { + LayerFound = &Layers_RenderThread[LayerIndex]; + } + } + } + else if (IsInRHIThread()) + { + for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++) + { + if (Layers_RHIThread[LayerIndex]->GetId() == LayerId) + { + LayerFound = &Layers_RHIThread[LayerIndex]; + } + } + } + else + { + return; + } + + if (LayerFound && (*LayerFound)->GetSwapChain().IsValid()) + { + bool bRightTexture = (*LayerFound)->GetRightSwapChain().IsValid(); + const IStereoLayers::FLayerDesc& Desc = (*LayerFound)->GetDesc(); + + if (Desc.HasShape<FCubemapLayer>()) + { + if (bRightTexture) + { + Texture = (*LayerFound)->GetRightSwapChain()->GetTextureCube(); + LeftTexture = (*LayerFound)->GetSwapChain()->GetTextureCube(); + } + else + { + Texture = LeftTexture = (*LayerFound)->GetSwapChain()->GetTextureCube(); + } + } + else if (Desc.HasShape<FCylinderLayer>() || Desc.HasShape<FQuadLayer>()) + { + if (bRightTexture) + { + Texture = (*LayerFound)->GetRightSwapChain()->GetTexture2D(); + LeftTexture = (*LayerFound)->GetSwapChain()->GetTexture2D(); + } + else + { + Texture = LeftTexture = (*LayerFound)->GetSwapChain()->GetTexture2D(); + } + } + } + } + + IStereoLayers::FLayerDesc FOculusXRHMD::GetDebugCanvasLayerDesc(FTextureRHIRef Texture) + { + IStereoLayers::FLayerDesc StereoLayerDesc(FCylinderLayer(100.f, 488.f / 4, 180.f)); + StereoLayerDesc.Transform = FTransform(FVector(0.f, 0, 0)); //100/0/0 for quads + StereoLayerDesc.QuadSize = FVector2D(180.f, 180.f); + StereoLayerDesc.PositionType = IStereoLayers::ELayerType::FaceLocked; + StereoLayerDesc.LayerSize = Texture->GetTexture2D()->GetSizeXY(); + StereoLayerDesc.Flags = IStereoLayers::ELayerFlags::LAYER_FLAG_TEX_CONTINUOUS_UPDATE; + StereoLayerDesc.Flags |= IStereoLayers::ELayerFlags::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO; + return StereoLayerDesc; + } + + void FOculusXRHMD::SetupViewFamily(FSceneViewFamily& InViewFamily) + { + } + + + void FOculusXRHMD::SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) + { + CheckInGameThread(); + } + + + void FOculusXRHMD::BeginRenderViewFamily(FSceneViewFamily& InViewFamily) + { + CheckInGameThread(); + + if (Settings.IsValid() && Settings->IsStereoEnabled()) + { + Settings->CurrentShaderPlatform = InViewFamily.Scene->GetShaderPlatform(); + Settings->Flags.bsRGBEyeBuffer = IsMobilePlatform(Settings->CurrentShaderPlatform) && IsMobileColorsRGB(); + + if (NextFrameToRender.IsValid()) + { + NextFrameToRender->ShowFlags = InViewFamily.EngineShowFlags; + } + + if (SpectatorScreenController != nullptr) + { + SpectatorScreenController->BeginRenderViewFamily(); + } + } + + StartRenderFrame_GameThread(); + } + + void FOculusXRHMD::UpdateInsightPassthrough() + { + const bool bShouldEnable = (InsightInitStatus == FInsightInitStatus::NotInitialized) && + (Settings_RenderThread->Flags.bInsightPassthroughEnabled); + + if (bShouldEnable) + { + if(OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().InitializeInsightPassthrough())) + { + UE_LOG(LogHMD, Log, TEXT("Passthrough Initialized")); + InsightInitStatus = FInsightInitStatus::Initialized; + } + else + { + InsightInitStatus = FInsightInitStatus::Failed; + UE_LOG(LogHMD, Log, TEXT("Passthrough initialization failed")); + } + } + else + { + const bool bShouldShutdown = (InsightInitStatus == FInsightInitStatus::Initialized) && + (!Settings_RenderThread->Flags.bInsightPassthroughEnabled); + if (bShouldShutdown) + { + ShutdownInsightPassthrough(); + } + } + } + + void FOculusXRHMD::ShutdownInsightPassthrough() + { + if (InsightInitStatus == FInsightInitStatus::Initialized ) + { + // it may already be deinitialized. + if(!FOculusXRHMDModule::GetPluginWrapper().GetInsightPassthroughInitialized() || + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().ShutdownInsightPassthrough())) + { + UE_LOG(LogHMD, Log, TEXT("Passthrough shutdown")); + InsightInitStatus = FInsightInitStatus::NotInitialized; + } + else + { + UE_LOG(LogHMD, Log, TEXT("Failed to shut down passthrough. It may be still in use.")); + } + } + } + + void FOculusXRHMD::PreRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& ViewFamily) + { + CheckInRenderThread(); + } + + void FOculusXRHMD::OnBeginRendering_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily) + { + CheckInRenderThread(); + + if (!Frame_RenderThread.IsValid()) + { + return; + } + + if (!Settings_RenderThread.IsValid() || !Settings_RenderThread->IsStereoEnabled()) + { + return; + } + + // If using OVRPlugin OpenXR, only update spectator screen mode with VR focus, since we are running the frameloop + // and cycling through the swapchain even without VR focus with OVRPlugin OpenXR + ovrpXrApi NativeXrApi; + FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi); + if (SpectatorScreenController && (NativeXrApi != ovrpXrApi_OpenXR || FApp::HasVRFocus())) + { + SpectatorScreenController->UpdateSpectatorScreenMode_RenderThread(); + Frame_RenderThread->Flags.bSpectatorScreenActive = SpectatorScreenController->GetSpectatorScreenMode() != ESpectatorScreenMode::Disabled; + } + + // Update mirror texture + CustomPresent->UpdateMirrorTexture_RenderThread(); + +#if !PLATFORM_ANDROID + #if 0 // The entire target should be cleared by the tonemapper and pp material + // Clear the padding between two eyes + const int32 GapMinX = ViewFamily.Views[0]->UnscaledViewRect.Max.X; + const int32 GapMaxX = ViewFamily.Views[1]->UnscaledViewRect.Min.X; + + if (GapMinX < GapMaxX) + { + SCOPED_DRAW_EVENT(RHICmdList, OculusClearQuad) + + const int32 GapMinY = ViewFamily.Views[0]->UnscaledViewRect.Min.Y; + const int32 GapMaxY = ViewFamily.Views[1]->UnscaledViewRect.Max.Y; + + FRHIRenderPassInfo RPInfo(ViewFamily.RenderTarget->GetRenderTargetTexture(), ERenderTargetActions::DontLoad_Store); + RHICmdList.BeginRenderPass(RPInfo, TEXT("Clear")); + { + RHICmdList.SetViewport(GapMinX, GapMinY, 0, GapMaxX, GapMaxY, 1.0f); + DrawClearQuad(RHICmdList, FLinearColor::Black); + } + RHICmdList.EndRenderPass(); + } + #endif +#else + // ensure we have attached JNI to this thread - this has to happen persistently as the JNI could detach if the app loses focus + FAndroidApplication::GetJavaEnv(); +#endif + + UpdateInsightPassthrough(); + + // Start RHI frame + StartRHIFrame_RenderThread(); + + // Update performance stats + PerformanceStats.Frames++; + PerformanceStats.Seconds = FPlatformTime::Seconds(); + } + + + void FOculusXRHMD::PreRenderView_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView) + { + } + + + void FOculusXRHMD::PostRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily) + { + CheckInRenderThread(); + + if (InViewFamily.Views[0]->StereoPass != EStereoscopicPass::eSSP_FULL) + { + FinishRenderFrame_RenderThread(GraphBuilder); + } + } + + void FOculusXRHMD::PostRenderBasePassMobile_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView) + { + UpdateFoveationOffsets_RenderThread(); + } + + int32 FOculusXRHMD::GetPriority() const + { + // We want to run after the FDefaultXRCamera's view extension + return -1; + } + + + bool FOculusXRHMD::IsActiveThisFrame_Internal(const FSceneViewExtensionContext& Context) const + { + // We need to use GEngine->IsStereoscopic3D in case the current viewport disallows running in stereo. + return GEngine && GEngine->IsStereoscopic3D(Context.Viewport); + } + +#ifdef WITH_OCULUS_LATE_LATCHING + bool FOculusXRHMD::LateLatchingEnabled() const + { +#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN && PLATFORM_ANDROID + // No LateLatching supported when occlusion culling is enabled due to mid frame submission + // No LateLatching supported for non Multi view ATM due to viewUniformBuffer reusing. + // The setting can be disabled in FOculusXRHMD::UpdateStereoRenderingParams + return Settings->bLateLatching; +#else + return false; +#endif + } + + void FOculusXRHMD::PreLateLatchingViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) + { + CheckInRenderThread(); + FGameFrame* CurrentFrame = GetFrame_RenderThread(); + if (CurrentFrame) + { + CurrentFrame->Flags.bRTLateUpdateDone = false; // Allow LateLatching to update poses again + } + } +#endif + + bool FOculusXRHMD::SupportsSpaceWarp() const + { +#if PLATFORM_ANDROID + // Use All static value here since those can't be change at runtime + ensureMsgf(CustomPresent.IsValid(), TEXT("SupportsSpaceWarp can only be called post CustomPresent created")); + const bool bOvrPlugin_OpenXR = Settings->XrApi == EOculusXRXrApi::OVRPluginOpenXR; + static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView")); + static const auto CVarSupportMobileSpaceWarp = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.SupportMobileSpaceWarp")); + bool bIsMobileMultiViewEnabled = (CVarMobileMultiView && CVarMobileMultiView->GetValueOnAnyThread() != 0); + bool bIsUsingMobileMultiView = GSupportsMobileMultiView && bIsMobileMultiViewEnabled; + bool bIsVulkan = CustomPresent->GetRenderAPI() == ovrpRenderAPI_Vulkan; + bool spaceWarpSupported = bOvrPlugin_OpenXR && bIsVulkan && bIsUsingMobileMultiView && CVarSupportMobileSpaceWarp && (CVarSupportMobileSpaceWarp->GetValueOnAnyThread() != 0); + return spaceWarpSupported; +#else + return false; +#endif + } + + FOculusXRHMD::FOculusXRHMD(const FAutoRegister& AutoRegister) + : FHeadMountedDisplayBase(nullptr) + , FHMDSceneViewExtension(AutoRegister) + , ConsoleCommands(this) + , InsightInitStatus(FInsightInitStatus::NotInitialized) + , bShutdownRequestQueued(false) + { + Flags.Raw = 0; + OCFlags.Raw = 0; + TrackingOrigin = EHMDTrackingOrigin::Type::Eye; + DeltaControlRotation = FRotator::ZeroRotator; // used from ApplyHmdRotation + LastPlayerOrientation = FQuat::Identity; + LastPlayerLocation = FVector::ZeroVector; + CachedWindowSize = FVector2D::ZeroVector; + CachedWorldToMetersScale = 100.0f; + LastTrackingToWorld = FTransform::Identity; + + NextFrameNumber = 0; + WaitFrameNumber = (uint32)-1; + NextLayerId = 0; + + Settings = CreateNewSettings(); + + RendererModule = nullptr; + + SplashLayerHandle = -1; + + SplashRotation = FRotator(); + + bIsStandaloneStereoOnlyDevice = IHeadMountedDisplayModule::IsAvailable() && IHeadMountedDisplayModule::Get().IsStandaloneStereoOnlyDevice(); + } + + + FOculusXRHMD::~FOculusXRHMD() + { + Shutdown(); + } + + + bool FOculusXRHMD::Startup() + { + if (GIsEditor) + { + Settings->Flags.bHeadTrackingEnforced = true; + } + + + check(!CustomPresent.IsValid()); + + FString RHIString; + { + FString HardwareDetails = FHardwareInfo::GetHardwareDetailsString(); + FString RHILookup = NAME_RHI.ToString() + TEXT("="); + + if (!FParse::Value(*HardwareDetails, *RHILookup, RHIString)) + { + return false; + } + } + +#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 + if (RHIString == TEXT("D3D11")) + { + CustomPresent = CreateCustomPresent_D3D11(this); + } + else +#endif +#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 + if (RHIString == TEXT("D3D12")) + { + CustomPresent = CreateCustomPresent_D3D12(this); + } + else +#endif +#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN + if (RHIString == TEXT("Vulkan")) + { + CustomPresent = CreateCustomPresent_Vulkan(this); + } + else +#endif + { + UE_LOG(LogHMD, Warning, TEXT("%s is not currently supported by OculusXRHMD plugin"), *RHIString); + return false; + } + + // grab a pointer to the renderer module for displaying our mirror window + static const FName RendererModuleName("Renderer"); + RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName); + +#if PLATFORM_ANDROID + // register our application lifetime delegates + FCoreDelegates::ApplicationWillEnterBackgroundDelegate.AddRaw(this, &FOculusXRHMD::ApplicationPauseDelegate); + FCoreDelegates::ApplicationHasEnteredForegroundDelegate.AddRaw(this, &FOculusXRHMD::ApplicationResumeDelegate); +#endif + + // Create eye layer + IStereoLayers::FLayerDesc EyeLayerDesc; + EyeLayerDesc.Priority = INT_MIN; + EyeLayerDesc.Flags = LAYER_FLAG_TEX_CONTINUOUS_UPDATE; + uint32 EyeLayerId = CreateLayer(EyeLayerDesc); + check(EyeLayerId == 0); + + Splash = MakeShareable(new FSplash(this)); + Splash->Startup(); + +#if !PLATFORM_ANDROID + SpectatorScreenController = MakeUnique<FSpectatorScreenController>(this); +#endif + UE_LOG(LogHMD, Log, TEXT("Oculus plugin initialized. Version: %s"), *GetVersionString()); + + return true; + } + + + void FOculusXRHMD::PreShutdown() + { + if (Splash.IsValid()) + { + Splash->PreShutdown(); + } + } + + + void FOculusXRHMD::Shutdown() + { + CheckInGameThread(); + + if (Splash.IsValid()) + { + Splash->Shutdown(); + Splash = nullptr; + // The base implementation stores a raw pointer to the Splash object and tries to deallocate it in its destructor + LoadingScreen = nullptr; + } + + if (CustomPresent.IsValid()) + { + CustomPresent->Shutdown(); + CustomPresent = nullptr; + } + + ReleaseDevice(); + + Settings.Reset(); + LayerMap.Reset(); + } + + void FOculusXRHMD::ApplicationPauseDelegate() + { + ExecuteOnRenderThread([this]() + { + ExecuteOnRHIThread([this]() + { + FOculusXRHMDModule::GetPluginWrapper().DestroyDistortionWindow2(); + }); + }); + OCFlags.AppIsPaused = true; + } + + void FOculusXRHMD::ApplicationResumeDelegate() + { + if (OCFlags.AppIsPaused && !InitializeSession()) + { + UE_LOG(LogHMD, Log, TEXT("HMD initialization failed")); + } + OCFlags.AppIsPaused = false; + } + + static const FString EYE_TRACKING_PERMISSION_NAME("com.oculus.permission.EYE_TRACKING"); + + bool FOculusXRHMD::CheckEyeTrackingPermission(EOculusXRFoveatedRenderingMethod InFoveatedRenderingMethod) + { +#if PLATFORM_ANDROID + // Check and request eye tracking permissions, bind delegate for handling permission request result + if (!UAndroidPermissionFunctionLibrary::CheckPermission(EYE_TRACKING_PERMISSION_NAME)) + { + TArray<FString> Permissions; + Permissions.Add(EYE_TRACKING_PERMISSION_NAME); + UAndroidPermissionCallbackProxy* Proxy = UAndroidPermissionFunctionLibrary::AcquirePermissions(Permissions); + Proxy->OnPermissionsGrantedDelegate.AddLambda([this, InFoveatedRenderingMethod](const TArray<FString>& Permissions, const TArray<bool>& GrantResults) + { + int PermIndex = Permissions.Find(EYE_TRACKING_PERMISSION_NAME); + if (PermIndex != INDEX_NONE && GrantResults[PermIndex]) + { + UE_LOG(LogHMD, Verbose, TEXT("com.oculus.permission.EYE_TRACKING permission granted")); + FoveatedRenderingMethod = InFoveatedRenderingMethod; + FOculusEventDelegates::OculusEyeTrackingStateChanged.Broadcast(true); + } + else + { + UE_LOG(LogHMD, Log, TEXT("com.oculus.permission.EYE_TRACKING permission denied")); + if (InFoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering) + { + EyeTrackedFoveatedRenderingFallback(); + } + FOculusEventDelegates::OculusEyeTrackingStateChanged.Broadcast(false); + } + }); + return false; + } +#endif // PLATFORM_ANDROID + return true; + } + + bool FOculusXRHMD::InitializeSession() + { + UE_LOG(LogHMD, Log, TEXT("Initializing OVRPlugin session")); + + if (!FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { +#if !UE_BUILD_SHIPPING + ovrpLogCallback logCallback = OvrpLogCallback; +#else + ovrpLogCallback logCallback = nullptr; +#endif + +#if PLATFORM_ANDROID + void* activity = (void*) FAndroidApplication::GetGameActivityThis(); +#else + void* activity = nullptr; +#endif + + int initializeFlags = GIsEditor ? ovrpInitializeFlag_SupportsVRToggle : 0; + + initializeFlags |= CustomPresent->SupportsSRGB() ? ovrpInitializeFlag_SupportSRGBFrameBuffer : 0; + + if (Settings->Flags.bSupportsDash) + { + initializeFlags |= ovrpInitializeFlag_FocusAware; + } + + if (SupportsSpaceWarp()) // Configure for space warp + { + initializeFlags |= ovrpInitializeFlag_SupportAppSpaceWarp; + UE_LOG(LogHMD, Log, TEXT("[Mobile SpaceWarp] Application is configured to support mobile spacewarp")); + } + bNeedReAllocateMotionVectorTexture_RenderThread = false; + +#if PLATFORM_WINDOWS + if (!FOculusXRHMDModule::Get().PreInit()) + { + return false; + } +#endif + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().Initialize7( + CustomPresent->GetRenderAPI(), + logCallback, + activity, + CustomPresent->GetOvrpInstance(), + CustomPresent->GetOvrpPhysicalDevice(), + CustomPresent->GetOvrpDevice(), + CustomPresent->GetOvrpCommandQueue(), + nullptr /*vkGetInstanceProcAddr*/, + 0 /*vkQueueFamilyIndex*/, + nullptr /*d3dDevice*/, + initializeFlags, + { OVRP_VERSION }))) + { + return false; + } + + ovrpBool Supported = ovrpBool_False; + if (Settings->bSupportEyeTrackedFoveatedRendering) + { + FOculusXRHMDModule::GetPluginWrapper().GetFoveationEyeTrackedSupported(&Supported); + } + bEyeTrackedFoveatedRenderingSupported = Supported == ovrpBool_True; + SetFoveatedRenderingMethod(Settings->FoveatedRenderingMethod); + SetFoveatedRenderingLevel(Settings->FoveatedRenderingLevel, Settings->bDynamicFoveatedRendering); + } + + FOculusXRHMDModule::GetPluginWrapper().SetAppEngineInfo2( + "Unreal Engine", + TCHAR_TO_ANSI(*FEngineVersion::Current().ToString()), + GIsEditor ? ovrpBool_True : ovrpBool_False); + + int flag = ovrpDistortionWindowFlag_None; + + FOculusXRHMDModule::GetPluginWrapper().SetupDistortionWindow3(flag); + FOculusXRHMDModule::GetPluginWrapper().SetSuggestedCpuPerformanceLevel((ovrpProcessorPerformanceLevel)Settings->SuggestedCpuPerfLevel); + FOculusXRHMDModule::GetPluginWrapper().SetSuggestedGpuPerformanceLevel((ovrpProcessorPerformanceLevel)Settings->SuggestedGpuPerfLevel); + FOculusXRHMDModule::GetPluginWrapper().SetFoveationEyeTracked(FoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering); + FOculusXRHMDModule::GetPluginWrapper().SetTiledMultiResLevel((ovrpTiledMultiResLevel)FoveatedRenderingLevel.load()); + FOculusXRHMDModule::GetPluginWrapper().SetTiledMultiResDynamic(bDynamicFoveatedRendering.load()); + FOculusXRHMDModule::GetPluginWrapper().SetAppCPUPriority2(ovrpBool_True); + FOculusXRHMDModule::GetPluginWrapper().SetLocalDimming(ovrpBool_True); + + OCFlags.NeedSetTrackingOrigin = true; + + NextFrameNumber = 0; + WaitFrameNumber = (uint32)-1; + + FOculusXRHMDModule::GetPluginWrapper().SetClientColorDesc((ovrpColorSpace)Settings->ColorSpace); + + return true; + } + + + void FOculusXRHMD::ShutdownSession() + { + ExecuteOnRenderThread([this]() + { + ExecuteOnRHIThread([this]() + { + FOculusXRHMDModule::GetPluginWrapper().DestroyDistortionWindow2(); + }); + }); + + FOculusXRHMDModule::GetPluginWrapper().Shutdown2(); + } + + bool FOculusXRHMD::InitDevice() + { + CheckInGameThread(); + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + // Already created and present + return true; + } + + if (!IsHMDEnabled()) + { + // Don't bother if HMD is not connected + return false; + } + + LoadFromSettings(); + + if (!InitializeSession()) + { + UE_LOG(LogHMD, Log, TEXT("HMD initialization failed")); + return false; + } + + // Don't need to reset these flags on application resume, so put them in InitDevice instead of InitializeSession + bNeedReAllocateViewportRenderTarget = true; + bNeedReAllocateDepthTexture_RenderThread = false; + bNeedReAllocateFoveationTexture_RenderThread = false; + Flags.bNeedDisableStereo = false; + OCFlags.NeedSetFocusToGameViewport = true; + + if (!CustomPresent->IsUsingCorrectDisplayAdapter()) + { + UE_LOG(LogHMD, Error, TEXT("Using incorrect display adapter for HMD.")); + ShutdownSession(); + return false; + } + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetSystemHeadsetType2(&Settings->SystemHeadset))) + { + Settings->SystemHeadset = ovrpSystemHeadset_None; + } + + FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, 0, 0.0); + + UpdateHmdRenderInfo(); + UpdateStereoRenderingParams(); + + ExecuteOnRenderThread([this](FRHICommandListImmediate& RHICmdList) + { + InitializeEyeLayer_RenderThread(RHICmdList); + }); + + if (!EyeLayer_RenderThread.IsValid() || !EyeLayer_RenderThread->GetSwapChain().IsValid()) + { + UE_LOG(LogHMD, Error, TEXT("Failed to create eye layer swap chain.")); + ShutdownSession(); + return false; + } + + if (!HiddenAreaMeshes[0].IsValid() || !HiddenAreaMeshes[1].IsValid()) + { + SetupOcclusionMeshes(); + } + +#if !UE_BUILD_SHIPPING + DrawDebugDelegateHandle = UDebugDrawService::Register(TEXT("Game"), FDebugDrawDelegate::CreateRaw(this, &FOculusXRHMD::DrawDebug)); +#endif + + // Do not set VR focus in Editor by just creating a device; Editor may have it created w/o requiring focus. + // Instead, set VR focus in OnBeginPlay (VR Preview will run there first). + if (!GIsEditor) + { + FApp::SetUseVRFocus(true); + FApp::SetHasVRFocus(true); + } + + FOculusXRHMDModule::GetPluginWrapper().SetClientColorDesc((ovrpColorSpace)Settings->ColorSpace); + + return true; + } + + + void FOculusXRHMD::ReleaseDevice() + { + CheckInGameThread(); + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + // Wait until the next frame before ending the session (workaround for DX12/Vulkan resources being ripped out from under us before we're done with them). + bShutdownRequestQueued = true; + } + } + + void BuildOcclusionMesh(FHMDViewMesh& Mesh, ovrpEye Eye, ovrpViewportStencilType MeshType) + { + int VertexCount = 0; + int IndexCount = 0; + + ovrpResult Result = ovrpResult::ovrpFailure; + if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().GetViewportStencil(Eye, MeshType, nullptr, &VertexCount, nullptr, &IndexCount))) + { + return; + } + + FRHIResourceCreateInfo CreateInfo(TEXT("FOculusXRHMD")); + Mesh.VertexBufferRHI = RHICreateVertexBuffer(sizeof(FFilterVertex) * VertexCount, BUF_Static, CreateInfo); + void* VoidPtr = RHILockBuffer(Mesh.VertexBufferRHI, 0, sizeof(FFilterVertex) * VertexCount, RLM_WriteOnly); + FFilterVertex* pVertices = reinterpret_cast<FFilterVertex*>(VoidPtr); + + Mesh.IndexBufferRHI = RHICreateIndexBuffer(sizeof(uint16), sizeof(uint16) * IndexCount, BUF_Static, CreateInfo); + void* VoidPtr2 = RHILockBuffer(Mesh.IndexBufferRHI, 0, sizeof(uint16) * IndexCount, RLM_WriteOnly); + uint16* pIndices = reinterpret_cast<uint16*>(VoidPtr2); + + ovrpVector2f* const ovrpVertices = new ovrpVector2f[VertexCount]; + + FOculusXRHMDModule::GetPluginWrapper().GetViewportStencil(Eye, MeshType, ovrpVertices, &VertexCount, pIndices, &IndexCount); + + for (int i = 0; i < VertexCount; ++i) + { + FFilterVertex& Vertex = pVertices[i]; + CA_SUPPRESS(6385); // warning C6385: Reading invalid data from 'ovrpVertices': the readable size is 'VertexCount*8' bytes, but '16' bytes may be read + const ovrpVector2f& Position = ovrpVertices[i]; + if (MeshType == ovrpViewportStencilType_HiddenArea) + { + Vertex.Position.X = (Position.x * 2.0f) - 1.0f; + Vertex.Position.Y = (Position.y * 2.0f) - 1.0f; + Vertex.Position.Z = 1.0f; + Vertex.Position.W = 1.0f; + Vertex.UV.X = 0.0f; + Vertex.UV.Y = 0.0f; + } + else if (MeshType == ovrpViewportStencilType_VisibleArea) + { + Vertex.Position.X = Position.x; + Vertex.Position.Y = 1.0f - Position.y; + Vertex.Position.Z = 0.0f; + Vertex.Position.W = 1.0f; + Vertex.UV.X = Position.x; + Vertex.UV.Y = 1.0f - Position.y; + } + else + { + check(0); + } + } + + Mesh.NumIndices = IndexCount; + Mesh.NumVertices = VertexCount; + Mesh.NumTriangles = IndexCount / 3; + + delete [] ovrpVertices; + + RHIUnlockBuffer(Mesh.VertexBufferRHI); + RHIUnlockBuffer(Mesh.IndexBufferRHI); + } + + void FOculusXRHMD::SetupOcclusionMeshes() + { + CheckInGameThread(); + + FOculusXRHMD* const Self = this; + ENQUEUE_RENDER_COMMAND(SetupOcclusionMeshesCmd)([Self](FRHICommandListImmediate& RHICmdList) + { + BuildOcclusionMesh(Self->HiddenAreaMeshes[0], ovrpEye_Left, ovrpViewportStencilType_HiddenArea); + BuildOcclusionMesh(Self->HiddenAreaMeshes[1], ovrpEye_Right, ovrpViewportStencilType_HiddenArea); + BuildOcclusionMesh(Self->VisibleAreaMeshes[0], ovrpEye_Left, ovrpViewportStencilType_VisibleArea); + BuildOcclusionMesh(Self->VisibleAreaMeshes[1], ovrpEye_Right, ovrpViewportStencilType_VisibleArea); + }); + } + + + static ovrpMatrix4f ovrpMatrix4f_Projection(const ovrpFrustum2f& frustum, bool leftHanded) + { + float handednessScale = leftHanded ? 1.0f : -1.0f; + + // A projection matrix is very like a scaling from NDC, so we can start with that. + float projXScale = 2.0f / (frustum.Fov.LeftTan + frustum.Fov.RightTan); + float projXOffset = (frustum.Fov.LeftTan - frustum.Fov.RightTan) * projXScale * 0.5f; + float projYScale = 2.0f / (frustum.Fov.UpTan + frustum.Fov.DownTan); + float projYOffset = (frustum.Fov.UpTan - frustum.Fov.DownTan) * projYScale * 0.5f; + + ovrpMatrix4f projection; + + // Produces X result, mapping clip edges to [-w,+w] + projection.M[0][0] = projXScale; + projection.M[0][1] = 0.0f; + projection.M[0][2] = handednessScale * projXOffset; + projection.M[0][3] = 0.0f; + + // Produces Y result, mapping clip edges to [-w,+w] + // Hey - why is that YOffset negated? + // It's because a projection matrix transforms from world coords with Y=up, + // whereas this is derived from an NDC scaling, which is Y=down. + projection.M[1][0] = 0.0f; + projection.M[1][1] = projYScale; + projection.M[1][2] = handednessScale * -projYOffset; + projection.M[1][3] = 0.0f; + + // Produces Z-buffer result + projection.M[2][0] = 0.0f; + projection.M[2][1] = 0.0f; + projection.M[2][2] = -handednessScale * frustum.zFar / (frustum.zNear - frustum.zFar); + projection.M[2][3] = (frustum.zFar * frustum.zNear) / (frustum.zNear - frustum.zFar); + + // Produces W result (= Z in) + projection.M[3][0] = 0.0f; + projection.M[3][1] = 0.0f; + projection.M[3][2] = handednessScale; + projection.M[3][3] = 0.0f; + + return projection; + } + + + void FOculusXRHMD::UpdateStereoRenderingParams() + { + CheckInGameThread(); + + // Update PixelDensity + bool bSupportsDepth = true; + + if (Settings->Flags.bPixelDensityAdaptive) + { + float AdaptiveGpuPerformanceScale = 1.0f; + FOculusXRHMDModule::GetPluginWrapper().GetAdaptiveGpuPerformanceScale2(&AdaptiveGpuPerformanceScale); + float NewPixelDensity = Settings->PixelDensity * FMath::Sqrt(AdaptiveGpuPerformanceScale); + NewPixelDensity = FMath::RoundToFloat(NewPixelDensity * 1024.0f) / 1024.0f; + Settings->SetPixelDensity(NewPixelDensity); + } + else + { + static const auto PixelDensityCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("vr.PixelDensity")); + Settings->SetPixelDensity(PixelDensityCVar ? PixelDensityCVar->GetFloat() : 1.0f); + + // Due to hijacking the depth target directly from the scene context, we can't support depth compositing if it's being scaled by screen percentage since it wont match our color render target dimensions. + static const auto ScreenPercentageCVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.ScreenPercentage")); + bSupportsDepth = !ScreenPercentageCVar || ScreenPercentageCVar->GetFloat() == 100.0f; + } + + // Update EyeLayer + FLayerPtr* EyeLayerFound = LayerMap.Find(0); + FLayer* EyeLayer = new FLayer(**EyeLayerFound); + *EyeLayerFound = MakeShareable(EyeLayer); + + ovrpLayout Layout = ovrpLayout_DoubleWide; + + static const auto CVarMobileMultiView = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("vr.MobileMultiView")); + const bool bIsMobileMultiViewEnabled = (CVarMobileMultiView && CVarMobileMultiView->GetValueOnAnyThread() != 0); + + const bool bIsUsingMobileMultiView = (GSupportsMobileMultiView || GRHISupportsArrayIndexFromAnyShader) && bIsMobileMultiViewEnabled; + // for now only mobile rendering codepaths use the array rendering system, so PC-native should stay in doublewide + if (bIsUsingMobileMultiView && IsMobilePlatform(Settings->CurrentShaderPlatform)) + { + Layout = ovrpLayout_Array; + } + +#if PLATFORM_ANDROID + static const auto CVarMobileHDR = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MobileHDR")); + const bool bMobileHDR = CVarMobileHDR && CVarMobileHDR->GetValueOnAnyThread() == 1; + + if (bMobileHDR) + { + static bool bDisplayedHDRError = false; + UE_CLOG(!bDisplayedHDRError, LogHMD, Error, TEXT("Mobile HDR is not supported on Oculus Mobile HMD devices.")); + bDisplayedHDRError = true; + } + + if (!bIsUsingMobileMultiView && Settings->bLateLatching) + { + UE_CLOG(true, LogHMD, Error, TEXT("LateLatching can't be used when Multiview is off, force disabling.")); + Settings->bLateLatching = false; + } + + static const auto AllowOcclusionQueriesCVar = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.AllowOcclusionQueries")); + const bool bAllowOcclusionQueries = AllowOcclusionQueriesCVar && (AllowOcclusionQueriesCVar->GetValueOnAnyThread() != 0); + if (bAllowOcclusionQueries && Settings->bLateLatching) + { + UE_CLOG(true, LogHMD, Error, TEXT("LateLatching can't used when Occlusion culling is on due to mid frame vkQueueSubmit, force disabling")); + Settings->bLateLatching = false; + } +#endif + + const bool bForceSymmetric = CVarOculusForceSymmetric.GetValueOnAnyThread() == 1 && (Layout == ovrpLayout_Array); + + ovrpLayerDesc_EyeFov EyeLayerDesc; + const bool requestsSubsampled = CVarOculusEnableSubsampledLayout.GetValueOnAnyThread() == 1 && CustomPresent->SupportsSubsampled(); + int eyeLayerFlags = requestsSubsampled ? ovrpLayerFlag_Subsampled : 0; + + ovrpTextureFormat MvPixelFormat = ovrpTextureFormat_R16G16B16A16_FP; + ovrpTextureFormat MvDepthFormat = ovrpTextureFormat_D24_S8; + int SpaceWarpAllocateFlag = 0; + + if (SupportsSpaceWarp()) + { + SpaceWarpAllocateFlag = ovrpLayerFlag_SpaceWarpDataAllocation | ovrpLayerFlag_SpaceWarpDedicatedDepth; + + bool spaceWarpEnabledByUser = CVarOculusEnableSpaceWarpUser.GetValueOnAnyThread()!=0; + bool spaceWarpEnabledInternal = CVarOculusEnableSpaceWarpInternal.GetValueOnAnyThread()!=0; + if (spaceWarpEnabledByUser != spaceWarpEnabledInternal) + { + CVarOculusEnableSpaceWarpInternal->Set(spaceWarpEnabledByUser); + } + } + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().CalculateEyeLayerDesc3( + Layout, + Settings->Flags.bPixelDensityAdaptive ? Settings->PixelDensityMax : Settings->PixelDensity, + Settings->Flags.bHQDistortion ? 0 : 1, + 1, // UNDONE + CustomPresent->GetOvrpTextureFormat(CustomPresent->GetDefaultPixelFormat(), Settings->Flags.bsRGBEyeBuffer), + (Settings->Flags.bCompositeDepth && bSupportsDepth) ? CustomPresent->GetDefaultDepthOvrpTextureFormat() : ovrpTextureFormat_None, + MvPixelFormat, + MvDepthFormat, + 1.0f, + CustomPresent->GetLayerFlags() | eyeLayerFlags | SpaceWarpAllocateFlag, + &EyeLayerDesc))) + { + ovrpFovf FrameFov[ovrpEye_Count] = { EyeLayerDesc.Fov[0], EyeLayerDesc.Fov[1] }; + if (bForceSymmetric) + { + // calculate symmetric FOV from runtime-provided asym in EyeLayerDesc + FrameFov[0].RightTan = FrameFov[1].RightTan = FMath::Max(EyeLayerDesc.Fov[0].RightTan, EyeLayerDesc.Fov[1].RightTan); + FrameFov[0].LeftTan = FrameFov[1].LeftTan = FMath::Max(EyeLayerDesc.Fov[0].LeftTan, EyeLayerDesc.Fov[1].LeftTan); + + const float asymTanSize = EyeLayerDesc.Fov[0].RightTan + EyeLayerDesc.Fov[0].LeftTan; + const float symTanSize = FrameFov[0].RightTan + FrameFov[0].LeftTan; + + // compute new resolution from a number of tile multiple, and the increase in width from symmetric FOV + const int numberTiles = (int)floor(EyeLayerDesc.TextureSize.w * symTanSize / ( 96.0 * asymTanSize)); + EyeLayerDesc.TextureSize.w = EyeLayerDesc.MaxViewportSize.w = numberTiles * 96; + } + + // Update viewports + // Scaling for DynamicResolution will happen later - see FSceneRenderer::PrepareViewRectsForRendering. + // If scaling does occur, EyeRenderViewport will be updated in FOculusXRHMD::SetFinalViewRect. + ovrpRecti vpRect[2]; + FOculusXRHMDModule::GetPluginWrapper().CalculateEyeViewportRect(EyeLayerDesc, ovrpEye_Left, 1.0f, &vpRect[0]); + FOculusXRHMDModule::GetPluginWrapper().CalculateEyeViewportRect(EyeLayerDesc, ovrpEye_Right, 1.0f, &vpRect[1]); + + if (Settings->Flags.bPixelDensityAdaptive) + { + vpRect[0].Size.w = vpRect[1].Size.w = ((int)(vpRect[0].Size.w / Settings->PixelDensityMax) + 3) & ~3; + vpRect[0].Size.h = vpRect[1].Size.h = ((int)(vpRect[0].Size.h / Settings->PixelDensityMax) + 3) & ~3; + + EyeLayerDesc.MaxViewportSize.w = ((int)(vpRect[0].Size.w * Settings->PixelDensityMax) + 3) & ~3; + EyeLayerDesc.MaxViewportSize.h = ((int)(vpRect[0].Size.h * Settings->PixelDensityMax) + 3) & ~3; + } + + // Unreal assumes no gutter between eyes + EyeLayerDesc.TextureSize.w = EyeLayerDesc.MaxViewportSize.w; + EyeLayerDesc.TextureSize.h = EyeLayerDesc.MaxViewportSize.h; + + if (Layout == ovrpLayout_DoubleWide) + { + vpRect[1].Pos.x = vpRect[0].Size.w; + EyeLayerDesc.TextureSize.w *= 2; + } + + { + // if using symmetric rendering, only send UVs of the asymmetrical subrect (the rest isn't useful) to the VR runtime + const float asymTanSize = EyeLayerDesc.Fov[0].RightTan + EyeLayerDesc.Fov[0].LeftTan; + const float symTanSize = FrameFov[0].RightTan + FrameFov[0].LeftTan; + + ovrpRecti vpRectSubmit[2] = { vpRect[0], vpRect[1] }; + vpRectSubmit[0].Pos.x += (FrameFov[0].LeftTan - EyeLayerDesc.Fov[0].LeftTan) * EyeLayerDesc.TextureSize.w / symTanSize; + vpRectSubmit[1].Pos.x += (FrameFov[1].LeftTan - EyeLayerDesc.Fov[1].LeftTan) * EyeLayerDesc.TextureSize.w / symTanSize; + vpRectSubmit[0].Size.w *= asymTanSize / symTanSize; + vpRectSubmit[1].Size.w *= asymTanSize / symTanSize; + EyeLayer->SetEyeLayerDesc(EyeLayerDesc, vpRectSubmit); + } + EyeLayer->bNeedsTexSrgbCreate = Settings->Flags.bsRGBEyeBuffer; + + Settings->RenderTargetSize = FIntPoint(EyeLayerDesc.TextureSize.w, EyeLayerDesc.TextureSize.h); + Settings->EyeRenderViewport[0].Min = FIntPoint(vpRect[0].Pos.x, vpRect[0].Pos.y); + Settings->EyeRenderViewport[0].Max = Settings->EyeRenderViewport[0].Min + FIntPoint(vpRect[0].Size.w, vpRect[0].Size.h); + Settings->EyeRenderViewport[1].Min = FIntPoint(vpRect[1].Pos.x, vpRect[1].Pos.y); + Settings->EyeRenderViewport[1].Max = Settings->EyeRenderViewport[1].Min + FIntPoint(vpRect[1].Size.w, vpRect[1].Size.h); + + Settings->EyeUnscaledRenderViewport[0] = Settings->EyeRenderViewport[0]; + Settings->EyeUnscaledRenderViewport[1] = Settings->EyeRenderViewport[1]; + + // Update projection matrices + ovrpFrustum2f frustumLeft = { 0.001f, 1000.0f, FrameFov[0] }; + ovrpFrustum2f frustumRight = { 0.001f, 1000.0f, FrameFov[1] }; + ovrpFrustum2f frustumCenter = { 0.001f, 1000.0f,{ FrameFov[0].UpTan, FrameFov[0].DownTan, FrameFov[0].LeftTan, FrameFov[1].RightTan } }; + + Settings->EyeProjectionMatrices[0] = ovrpMatrix4f_Projection(frustumLeft, true); + Settings->EyeProjectionMatrices[1] = ovrpMatrix4f_Projection(frustumRight, true); + Settings->MonoProjectionMatrix = ovrpMatrix4f_Projection(frustumCenter, true); + + // given that we send a subrect in vpRectSubmit, the FOV is the default asym one in EyeLayerDesc, not FrameFov + if (Frame.IsValid()) + { + Frame->Fov[0] = EyeLayerDesc.Fov[0]; + Frame->Fov[1] = EyeLayerDesc.Fov[1]; + } + + // Flag if need to recreate render targets + if (!EyeLayer->CanReuseResources(EyeLayer_RenderThread.Get())) + { + AllocateEyeBuffer(); + } + } + } + + + void FOculusXRHMD::UpdateHmdRenderInfo() + { + CheckInGameThread(); + FOculusXRHMDModule::GetPluginWrapper().GetSystemDisplayFrequency2(&Settings->VsyncToNextVsync); + } + + + void FOculusXRHMD::InitializeEyeLayer_RenderThread(FRHICommandListImmediate& RHICmdList) + { + check(!InGameThread()); + CheckInRenderThread(); + + if (LayerMap[0].IsValid()) + { + FLayerPtr EyeLayer = LayerMap[0]->Clone(); + EyeLayer->Initialize_RenderThread(Settings_RenderThread.Get(), CustomPresent, &DeferredDeletion, RHICmdList, EyeLayer_RenderThread.Get()); + + if(Layers_RenderThread.Num() > 0) + { + Layers_RenderThread[0] = EyeLayer; + } + else + { + Layers_RenderThread.Add(EyeLayer); + } + + if (EyeLayer->GetDepthSwapChain().IsValid()) + { + if (!EyeLayer_RenderThread.IsValid() || EyeLayer->GetDepthSwapChain() != EyeLayer_RenderThread->GetDepthSwapChain()) + { + bNeedReAllocateDepthTexture_RenderThread = true; + } + } + if (EyeLayer->GetFoveationSwapChain().IsValid()) + { + if (!EyeLayer_RenderThread.IsValid() || EyeLayer->GetFoveationSwapChain() != EyeLayer_RenderThread->GetFoveationSwapChain()) + { + bNeedReAllocateFoveationTexture_RenderThread = true; + } + } + + if (EyeLayer->GetMotionVectorSwapChain().IsValid()) + { + if (!EyeLayer_RenderThread.IsValid() || EyeLayer->GetMotionVectorSwapChain() != EyeLayer_RenderThread->GetMotionVectorSwapChain() + || EyeLayer->GetMotionVectorDepthSwapChain() != EyeLayer_RenderThread->GetMotionVectorDepthSwapChain()) + { + bNeedReAllocateMotionVectorTexture_RenderThread = true; + UE_LOG(LogHMD, VeryVerbose, TEXT("[Mobile SpaceWarp] request to re-allocate motionVector textures")); + } + } + + if (EyeLayer_RenderThread.IsValid()) + { + DeferredDeletion.AddLayerToDeferredDeletionQueue(EyeLayer_RenderThread); + } + + EyeLayer_RenderThread = EyeLayer; + } + } + + + void FOculusXRHMD::ApplySystemOverridesOnStereo(bool force) + { + CheckInGameThread(); + // ALWAYS SET r.FinishCurrentFrame to 0! Otherwise the perf might be poor. + // @TODO: revise the FD3D11DynamicRHI::RHIEndDrawingViewport code (and other renderers) + // to ignore this var completely. + static const auto CFinishFrameVar = IConsoleManager::Get().FindConsoleVariable(TEXT("r.FinishCurrentFrame")); + CFinishFrameVar->Set(0); + } + + + bool FOculusXRHMD::OnOculusStateChange(bool bIsEnabledNow) + { + if (!bIsEnabledNow) + { + // Switching from stereo + ReleaseDevice(); + + ResetControlRotation(); + return true; + } + else + { + // Switching to stereo + if (InitDevice()) + { + Flags.bApplySystemOverridesOnStereo = true; + return true; + } + DeltaControlRotation = FRotator::ZeroRotator; + } + return false; + } + + + class FSceneViewport* FOculusXRHMD::FindSceneViewport() + { + if (!GIsEditor) + { + UGameEngine* GameEngine = Cast<UGameEngine>(GEngine); + return GameEngine->SceneViewport.Get(); + } +#if WITH_EDITOR + else + { + UEditorEngine* EditorEngine = CastChecked<UEditorEngine>(GEngine); + FSceneViewport* PIEViewport = (FSceneViewport*)EditorEngine->GetPIEViewport(); + if (PIEViewport != nullptr && PIEViewport->IsStereoRenderingAllowed()) + { + // PIE is setup for stereo rendering + return PIEViewport; + } + else + { + // Check to see if the active editor viewport is drawing in stereo mode + // @todo vreditor: Should work with even non-active viewport! + FSceneViewport* EditorViewport = (FSceneViewport*)EditorEngine->GetActiveViewport(); + if (EditorViewport != nullptr && EditorViewport->IsStereoRenderingAllowed()) + { + return EditorViewport; + } + } + } +#endif + return nullptr; + } + + + bool FOculusXRHMD::ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread() const + { + CheckInRenderThread(); + + // If you really need the eye corners to look nice, and can't just crop more, + // and are willing to suffer a frametime hit... you could do this: +#if 0 + switch(GetSpectatorScreenMode_RenderThread()) + { + case ESpectatorScreenMode::SingleEyeLetterboxed: + case ESpectatorScreenMode::SingleEyeCroppedToFill: + case ESpectatorScreenMode::TexturePlusEye: + return true; + } +#endif + + return false; + } + + + ESpectatorScreenMode FOculusXRHMD::GetSpectatorScreenMode_RenderThread() const + { + CheckInRenderThread(); + return SpectatorScreenController ? SpectatorScreenController->GetSpectatorScreenMode() : ESpectatorScreenMode::Disabled; + } + + +#if !UE_BUILD_SHIPPING + static const char* FormatLatencyReading(char* buff, size_t size, float val) + { + if (val < 0.000001f) + { + FCStringAnsi::Strcpy(buff, size, "N/A "); + } + else + { + FCStringAnsi::Snprintf(buff, size, "%4.2fms", val * 1000.0f); + } + return buff; + } + + + void FOculusXRHMD::DrawDebug(UCanvas* InCanvas, APlayerController* InPlayerController) + { + CheckInGameThread(); + + if (InCanvas && IsStereoEnabled() && Settings->Flags.bShowStats) + { + static const FColor TextColor(0, 255, 0); + // Pick a larger font on console. + UFont* const Font = FPlatformProperties::SupportsWindowedMode() ? GEngine->GetSmallFont() : GEngine->GetMediumFont(); + const int32 RowHeight = FMath::TruncToInt(Font->GetMaxCharHeight() * 1.1f); + + float ClipX = InCanvas->ClipX; + float ClipY = InCanvas->ClipY; + float LeftPos = 0; + + ClipX -= 100; + LeftPos = ClipX * 0.3f; + float TopPos = ClipY * 0.4f; + + int32 X = (int32)LeftPos; + int32 Y = (int32)TopPos; + + FString Str; + + if (!Settings->Flags.bPixelDensityAdaptive) + { + Str = FString::Printf(TEXT("PD: %.2f"), Settings->PixelDensity); + } + else + { + Str = FString::Printf(TEXT("PD: %.2f [%0.2f, %0.2f]"), Settings->PixelDensity, + Settings->PixelDensityMin, Settings->PixelDensityMax); + } + InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor); + Y += RowHeight; + + Str = FString::Printf(TEXT("W-to-m scale: %.2f uu/m"), GetWorldToMetersScale()); + InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor); + + ovrpAppLatencyTimings AppLatencyTimings; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppLatencyTimings2(&AppLatencyTimings))) + { + Y += RowHeight; + + char buf[5][20]; + char destStr[100]; + + FCStringAnsi::Snprintf(destStr, sizeof(destStr), "Latency, ren: %s tw: %s pp: %s err: %s %s", + FormatLatencyReading(buf[0], sizeof(buf[0]), AppLatencyTimings.LatencyRender), + FormatLatencyReading(buf[1], sizeof(buf[1]), AppLatencyTimings.LatencyTimewarp), + FormatLatencyReading(buf[2], sizeof(buf[2]), AppLatencyTimings.LatencyPostPresent), + FormatLatencyReading(buf[3], sizeof(buf[3]), AppLatencyTimings.ErrorRender), + FormatLatencyReading(buf[4], sizeof(buf[4]), AppLatencyTimings.ErrorTimewarp)); + + Str = ANSI_TO_TCHAR(destStr); + InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor); + } + + // Second row + X = (int32)LeftPos + 200; + Y = (int32)TopPos; + + Str = FString::Printf(TEXT("HQ dist: %s"), (Settings->Flags.bHQDistortion) ? TEXT("ON") : TEXT("OFF")); + InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor); + Y += RowHeight; + + float UserIPD; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserIPD2(&UserIPD))) + { + Str = FString::Printf(TEXT("IPD: %.2f mm"), UserIPD * 1000.f); + InCanvas->Canvas->DrawShadowedString(X, Y, *Str, Font, TextColor); + Y += RowHeight; + } + } + } +#endif // #if !UE_BUILD_SHIPPING + + FOculusXRHMD* FOculusXRHMD::GetOculusXRHMD() + { +#if OCULUS_HMD_SUPPORTED_PLATFORMS + if (GEngine && GEngine->XRSystem.IsValid()) + { + if (GEngine->XRSystem->GetSystemName() == OculusXRHMD::FOculusXRHMD::OculusSystemName) + { + return static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem.Get()); + } + } +#endif + return nullptr; + } + + bool FOculusXRHMD::IsHMDActive() const + { + return FOculusXRHMDModule::GetPluginWrapper().GetInitialized() != ovrpBool_False; + } + + float FOculusXRHMD::GetWorldToMetersScale() const + { + CheckInGameThread(); + + if (NextFrameToRender.IsValid()) + { + return NextFrameToRender->WorldToMetersScale; + } + + if (GWorld != nullptr) + { +#if WITH_EDITOR + // Workaround to allow WorldToMeters scaling to work correctly for controllers while running inside PIE. + // The main world will most likely not be pointing at the PIE world while polling input, so if we find a world context + // of that type, use that world's WorldToMeters instead. + if (GIsEditor) + { + for (const FWorldContext& Context : GEngine->GetWorldContexts()) + { + if (Context.WorldType == EWorldType::PIE) + { + return Context.World()->GetWorldSettings()->WorldToMeters; + } + } + } +#endif //WITH_EDITOR + + // We're not currently rendering a frame, so just use whatever world to meters the main world is using. + // This can happen when we're polling input in the main engine loop, before ticking any worlds. + return GWorld->GetWorldSettings()->WorldToMeters; + } + + return 100.0f; + } + + FVector FOculusXRHMD::GetNeckPosition(const FQuat& HeadOrientation, const FVector& HeadPosition) + { + CheckInGameThread(); + + FVector NeckPosition = HeadOrientation.Inverse().RotateVector(HeadPosition); + + ovrpVector2f NeckEyeDistance; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserNeckEyeDistance2(&NeckEyeDistance))) + { + const float WorldToMetersScale = GetWorldToMetersScale(); + NeckPosition.X -= NeckEyeDistance.x * WorldToMetersScale; + NeckPosition.Z -= NeckEyeDistance.y * WorldToMetersScale; + } + + return NeckPosition; + } + + + void FOculusXRHMD::SetBaseOffsetInMeters(const FVector& BaseOffset) + { + CheckInGameThread(); + + Settings->BaseOffset = BaseOffset; + } + + + FVector FOculusXRHMD::GetBaseOffsetInMeters() const + { + CheckInGameThread(); + + return Settings->BaseOffset; + } + + + bool FOculusXRHMD::ConvertPose(const ovrpPosef& InPose, FPose& OutPose) const + { + CheckInGameThread(); + + if (!NextFrameToRender.IsValid()) + { + return false; + } + + return ConvertPose_Internal(InPose, OutPose, Settings.Get(), NextFrameToRender->WorldToMetersScale); + } + + + bool FOculusXRHMD::ConvertPose(const FPose& InPose, ovrpPosef& OutPose) const + { + CheckInGameThread(); + + if (!NextFrameToRender.IsValid()) + { + return false; + } + + return ConvertPose_Internal(InPose, OutPose, Settings.Get(), NextFrameToRender->WorldToMetersScale); + } + + + bool FOculusXRHMD::ConvertPose_RenderThread(const ovrpPosef& InPose, FPose& OutPose) const + { + CheckInRenderThread(); + + if (!Frame_RenderThread.IsValid()) + { + return false; + } + + return ConvertPose_Internal(InPose, OutPose, Settings_RenderThread.Get(), Frame_RenderThread->WorldToMetersScale); + } + + + bool FOculusXRHMD::ConvertPose_Internal(const ovrpPosef& InPose, FPose& OutPose, const FSettings* Settings, float WorldToMetersScale) + { + return OculusXRHMD::ConvertPose_Internal(InPose, OutPose, Settings->BaseOrientation, Settings->BaseOffset, WorldToMetersScale); + } + + bool FOculusXRHMD::ConvertPose_Internal(const FPose& InPose, ovrpPosef& OutPose, const FSettings* Settings, float WorldToMetersScale) + { + return OculusXRHMD::ConvertPose_Internal(InPose, OutPose, Settings->BaseOrientation, Settings->BaseOffset, WorldToMetersScale); + } + + + FVector FOculusXRHMD::ScaleAndMovePointWithPlayer(ovrpVector3f& OculusXRHMDPoint) + { + CheckInGameThread(); + + FMatrix TranslationMatrix; + TranslationMatrix.SetIdentity(); + TranslationMatrix = TranslationMatrix.ConcatTranslation(LastPlayerLocation); + + FVector ConvertedPoint = ToFVector(OculusXRHMDPoint) * GetWorldToMetersScale(); + FRotator RotateWithPlayer = LastPlayerOrientation.Rotator(); + FVector TransformWithPlayer = RotateWithPlayer.RotateVector(ConvertedPoint); + TransformWithPlayer = FVector(TranslationMatrix.TransformPosition(TransformWithPlayer)); + + if (GetXRCamera(HMDDeviceId)->GetUseImplicitHMDPosition()) + { + FQuat HeadOrientation = FQuat::Identity; + FVector HeadPosition; + GetCurrentPose(HMDDeviceId, HeadOrientation, HeadPosition); + TransformWithPlayer -= RotateWithPlayer.RotateVector(HeadPosition); + } + + return TransformWithPlayer; + } + + ovrpVector3f FOculusXRHMD::WorldLocationToOculusPoint(const FVector& InUnrealPosition) + { + CheckInGameThread(); + FQuat AdjustedPlayerOrientation = GetBaseOrientation().Inverse() * LastPlayerOrientation; + AdjustedPlayerOrientation.Normalize(); + + FVector AdjustedPlayerLocation = LastPlayerLocation; + if (GetXRCamera(HMDDeviceId)->GetUseImplicitHMDPosition()) + { + FQuat HeadOrientation = FQuat::Identity; // Unused + FVector HeadPosition; + GetCurrentPose(HMDDeviceId, HeadOrientation, HeadPosition); + AdjustedPlayerLocation -= LastPlayerOrientation.Inverse().RotateVector(HeadPosition); + } + const FTransform InvWorldTransform = FTransform(AdjustedPlayerOrientation, AdjustedPlayerLocation).Inverse(); + const FVector ConvertedPosition = InvWorldTransform.TransformPosition(InUnrealPosition) / GetWorldToMetersScale(); + + return ToOvrpVector3f(ConvertedPosition); + } + + + float FOculusXRHMD::ConvertFloat_M2U(float OculusFloat) const + { + CheckInGameThread(); + + return OculusFloat * GetWorldToMetersScale(); + } + + + FVector FOculusXRHMD::ConvertVector_M2U(ovrpVector3f OculusXRHMDPoint) const + { + CheckInGameThread(); + + return ToFVector(OculusXRHMDPoint) * GetWorldToMetersScale(); + } + + + bool FOculusXRHMD::GetUserProfile(UserProfile& OutProfile) + { + float UserIPD; + ovrpVector2f UserNeckEyeDistance; + float UserEyeHeight; + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserIPD2(&UserIPD)) && + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserNeckEyeDistance2(&UserNeckEyeDistance)) && + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetUserEyeHeight2(&UserEyeHeight))) + { + OutProfile.IPD = UserIPD; + OutProfile.EyeDepth = UserNeckEyeDistance.x; + OutProfile.EyeHeight = UserEyeHeight; + return true; + } + + return false; + } + + + float FOculusXRHMD::GetVsyncToNextVsync() const + { + CheckInGameThread(); + + return Settings->VsyncToNextVsync; + } + + + FPerformanceStats FOculusXRHMD::GetPerformanceStats() const + { + return PerformanceStats; + } + + void FOculusXRHMD::GetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel& CpuPerfLevel, EOculusXRProcessorPerformanceLevel& GpuPerfLevel) + { + CheckInGameThread(); + CpuPerfLevel = Settings->SuggestedCpuPerfLevel; + GpuPerfLevel = Settings->SuggestedGpuPerfLevel; + } + + void FOculusXRHMD::SetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel CpuPerfLevel, EOculusXRProcessorPerformanceLevel GpuPerfLevel) + { + CheckInGameThread(); + FOculusXRHMDModule::GetPluginWrapper().SetSuggestedCpuPerformanceLevel((ovrpProcessorPerformanceLevel)CpuPerfLevel); + FOculusXRHMDModule::GetPluginWrapper().SetSuggestedGpuPerformanceLevel((ovrpProcessorPerformanceLevel)GpuPerfLevel); + Settings->SuggestedCpuPerfLevel = CpuPerfLevel; + Settings->SuggestedGpuPerfLevel = GpuPerfLevel; + } + + void FOculusXRHMD::SetFoveatedRenderingMethod(EOculusXRFoveatedRenderingMethod InFoveatedRenderingMethod) + { +#ifdef WITH_OCULUS_BRANCH + Settings->FoveatedRenderingMethod = InFoveatedRenderingMethod; + // Don't switch to eye tracked foveated rendering when it's not supported or permissions are denied + if (InFoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering && !(bEyeTrackedFoveatedRenderingSupported && CheckEyeTrackingPermission(InFoveatedRenderingMethod))) + { + return; + } +#else + Settings->FoveatedRenderingMethod = EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering; + if (InFoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering) + { + UE_LOG(LogHMD, Warning, TEXT("Eye Tracked Foveated Rendering is not supported on this engine version, using Fixed Foveated Rendering instead")); + } +#endif // WITH_OCULUS_BRANCH + + FoveatedRenderingMethod = Settings->FoveatedRenderingMethod; + } + + void FOculusXRHMD::SetFoveatedRenderingLevel(EOculusXRFoveatedRenderingLevel InFoveationLevel, bool isDynamic) + { + FoveatedRenderingLevel = Settings->FoveatedRenderingLevel = InFoveationLevel; + bDynamicFoveatedRendering = Settings->bDynamicFoveatedRendering = isDynamic; + } + + void FOculusXRHMD::SetColorScaleAndOffset(FLinearColor ColorScale, FLinearColor ColorOffset, bool bApplyToAllLayers) + { + CheckInGameThread(); + Settings->bApplyColorScaleAndOffsetToAllLayers = bApplyToAllLayers; + Settings->ColorScale = LinearColorToOvrpVector4f(ColorScale); + Settings->ColorOffset = LinearColorToOvrpVector4f(ColorOffset); + } + + bool FOculusXRHMD::DoEnableStereo(bool bStereo) + { + CheckInGameThread(); + + FSceneViewport* SceneVP = FindSceneViewport(); + + if (!Settings->Flags.bHMDEnabled || (SceneVP && !SceneVP->IsStereoRenderingAllowed())) + { + bStereo = false; + } + + if (Settings->Flags.bStereoEnabled && bStereo || !Settings->Flags.bStereoEnabled && !bStereo) + { + // already in the desired mode + return Settings->Flags.bStereoEnabled; + } + + TSharedPtr<SWindow> Window; + + if (SceneVP) + { + Window = SceneVP->FindWindow(); + } + + if (!Window.IsValid() || !SceneVP || !SceneVP->GetViewportWidget().IsValid()) + { + // try again next frame + if (bStereo) + { + Flags.bNeedEnableStereo = true; + + // a special case when stereo is enabled while window is not available yet: + // most likely this is happening from BeginPlay. In this case, if frame exists (created in OnBeginPlay) + // then we need init device and populate the initial tracking for head/hand poses. + if (Frame.IsValid()) + { + InitDevice(); + } + } + else + { + Flags.bNeedDisableStereo = true; + } + + return Settings->Flags.bStereoEnabled; + } + + if (OnOculusStateChange(bStereo)) + { + Settings->Flags.bStereoEnabled = bStereo; + + // Uncap fps to enable FPS higher than 62 + GEngine->bForceDisableFrameRateSmoothing = bStereo; + + // Set MirrorWindow state on the Window + Window->SetMirrorWindow(bStereo); + + if (bStereo) + { + // Start frame + StartGameFrame_GameThread(); + StartRenderFrame_GameThread(); + + // Set viewport size to Rift resolution + // NOTE: this can enqueue a render frame right away as a result (calling into FOculusXRHMD::BeginRenderViewFamily) + SceneVP->SetViewportSize(Settings->RenderTargetSize.X, Settings->RenderTargetSize.Y); + + if (Settings->Flags.bPauseRendering) + { + GEngine->SetMaxFPS(10); + } + + // Hook up dynamic res +#if !PLATFORM_ANDROID + GEngine->ChangeDynamicResolutionStateAtNextFrame(MakeShareable(new FDynamicResolutionState(Settings))); +#endif + } + else + { + // Work around an error log that can happen when enabling stereo rendering again + if (NextFrameNumber == WaitFrameNumber) + { + NextFrameNumber++; + } + + if (Settings->Flags.bPauseRendering) + { + GEngine->SetMaxFPS(0); + } + + // Restore viewport size to window size + FVector2D size = Window->GetSizeInScreen(); + SceneVP->SetViewportSize(size.X, size.Y); + Window->SetViewportSizeDrivenByWindow(true); + + // Restore default dynamic res +#if !PLATFORM_ANDROID + GEngine->ChangeDynamicResolutionStateAtNextFrame(FDynamicResolutionHeuristicProxy::CreateDefaultState()); +#endif + } + } + + return Settings->Flags.bStereoEnabled; + } + + void FOculusXRHMD::ResetControlRotation() const + { + // Switching back to non-stereo mode: reset player rotation and aim. + // Should we go through all playercontrollers here? + APlayerController* pc = GEngine->GetFirstLocalPlayerController(GWorld); + if (pc) + { + // Reset Aim? @todo + FRotator r = pc->GetControlRotation(); + r.Normalize(); + // Reset roll and pitch of the player + r.Roll = 0; + r.Pitch = 0; + pc->SetControlRotation(r); + } + } + + void FOculusXRHMD::UpdateFoveationOffsets_RenderThread() + { +#ifdef WITH_OCULUS_BRANCH + CheckInRenderThread(); + + SCOPED_NAMED_EVENT(UpdateFoveationOffsets_RenderThread, FColor::Red); + + // Don't execute anything if we're not using Eye Tracked Foveated Rendering (this already takes into account if it's supported or not) + if (!Frame_RenderThread.IsValid() || Frame_RenderThread->FoveatedRenderingMethod != EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering) + { + return; + } + + const FXRSwapChainPtr& SwapChain = EyeLayer_RenderThread->GetSwapChain(); + if (!SwapChain.IsValid()) + { + return; + } + + const FRHITexture2D* const SwapChainTexture = SwapChain->GetTexture2DArray() ? SwapChain->GetTexture2DArray() : SwapChain->GetTexture2D(); + if (!SwapChainTexture) + { + return; + } + const FIntPoint SwapChainDimensions = SwapChainTexture->GetSizeXY(); + + // Enqueue the actual update on the RHI thread, which should execute right before the EndRenderPass call + ExecuteOnRHIThread_DoNotWait([this, SwapChainDimensions]() + { + SCOPED_NAMED_EVENT(UpdateFoveationEyeTracked_RHIThread, FColor::Red); + + bool bUseOffsets = false; + FIntPoint Offsets[2]; + // Make sure the the Foveated Rendering Method is still eye tracked at RHI thread time before getting offsets. + // If the base setting was changed to fixed, even if the frame's setting is still eye tracked, we should switch to fixed. This + // usually indicates that eye tracking failed on the previous frame, so we don't need to try it again. + if (Frame_RHIThread.IsValid() && + Frame_RHIThread->FoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering && + FoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().SetFoveationEyeTracked(ovrpBool_True))) + { + ovrpVector2f fovCenter[2]; + ovrpResult Result = FOculusXRHMDModule::GetPluginWrapper().GetFoveationEyeTrackedCenter(fovCenter); + if (OVRP_SUCCESS(Result)) + { + Offsets[0].X = fovCenter[0].x * SwapChainDimensions.X / 2; + Offsets[0].Y = fovCenter[0].y * SwapChainDimensions.Y / 2; + Offsets[1].X = fovCenter[1].x * SwapChainDimensions.X / 2; + Offsets[1].Y = fovCenter[1].y * SwapChainDimensions.Y / 2; + bUseOffsets = true; + } + else if (Result != ovrpFailure_DataIsInvalid) + { + // Fall back to dynamic FFR High if OVRPlugin call actually fails, since we're not expecting GFR to work again. + // Additional rendering changes can be made by binding the changes to OculusEyeTrackingStateChanged + EyeTrackedFoveatedRenderingFallback(); + FOculusEventDelegates::OculusEyeTrackingStateChanged.Broadcast(false); + } + } + } + + if (CustomPresent) + { + CustomPresent->UpdateFoveationOffsets_RHIThread(bUseOffsets, Offsets); + } + }); +#endif // WITH_OCULUS_BRANCH + } + + FSettingsPtr FOculusXRHMD::CreateNewSettings() const + { + FSettingsPtr Result(MakeShareable(new FSettings())); + return Result; + } + + + FGameFramePtr FOculusXRHMD::CreateNewGameFrame() const + { + FGameFramePtr Result(MakeShareable(new FGameFrame())); + Result->FrameNumber = NextFrameNumber; + Result->WindowSize = CachedWindowSize; + Result->WorldToMetersScale = CachedWorldToMetersScale; + Result->NearClippingPlane = GNearClippingPlane; + // Allow CVars to override the app's foveated rendering settings (set -1 to restore app's setting) + Result->FoveatedRenderingMethod = CVarOculusFoveatedRenderingMethod.GetValueOnAnyThread() >= 0 ? (EOculusXRFoveatedRenderingMethod)CVarOculusFoveatedRenderingMethod.GetValueOnAnyThread() : FoveatedRenderingMethod.load(); + Result->FoveatedRenderingLevel = CVarOculusFoveatedRenderingLevel.GetValueOnAnyThread() >= 0 ? (EOculusXRFoveatedRenderingLevel)CVarOculusFoveatedRenderingLevel.GetValueOnAnyThread() : FoveatedRenderingLevel.load(); + Result->bDynamicFoveatedRendering = CVarOculusDynamicFoveatedRendering.GetValueOnAnyThread() >= 0 ? (bool)CVarOculusDynamicFoveatedRendering.GetValueOnAnyThread() : bDynamicFoveatedRendering.load(); + Result->Flags.bSplashIsShown = Splash->IsShown(); + return Result; + } + + + void FOculusXRHMD::StartGameFrame_GameThread() + { + CheckInGameThread(); + check(Settings.IsValid()); + + if (!Frame.IsValid()) + { + Splash->UpdateLoadingScreen_GameThread(); //the result of this is used in CreateGameFrame to know if Frame is a "real" one or a "splash" one. + if (Settings->Flags.bHMDEnabled) + { + Frame = CreateNewGameFrame(); + NextFrameToRender = Frame; + + UE_LOG(LogHMD, VeryVerbose, TEXT("StartGameFrame %u"), Frame->FrameNumber); + + if (!Splash->IsShown()) + { + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && WaitFrameNumber != Frame->FrameNumber) + { + SCOPED_NAMED_EVENT(WaitFrame, FColor::Red); + + UE_LOG(LogHMD, Verbose, TEXT("FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame %u"), Frame->FrameNumber); + + ovrpResult Result; + if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame(Frame->FrameNumber))) + { + UE_LOG(LogHMD, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame %u failed (%d)"), Frame->FrameNumber, Result); + } + else + { + WaitFrameNumber = Frame->FrameNumber; + } + } + + FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, Frame->FrameNumber, 0.0); + } + } + + UpdateStereoRenderingParams(); + } + } + + + void FOculusXRHMD::FinishGameFrame_GameThread() + { + CheckInGameThread(); + + if (Frame.IsValid()) + { + UE_LOG(LogHMD, VeryVerbose, TEXT("FinishGameFrame %u"), Frame->FrameNumber); + } + + Frame.Reset(); + } + + + void FOculusXRHMD::StartRenderFrame_GameThread() + { + CheckInGameThread(); + + if (NextFrameToRender.IsValid() && NextFrameToRender != LastFrameToRender) + { + UE_LOG(LogHMD, VeryVerbose, TEXT("StartRenderFrame %u"), NextFrameToRender->FrameNumber); + + LastFrameToRender = NextFrameToRender; + NextFrameToRender->Flags.bSplashIsShown = Splash->IsShown(); + + ovrpXrApi NativeXrApi; + FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi); + if ((NextFrameToRender->ShowFlags.Rendering || NativeXrApi == ovrpXrApi_OpenXR) && !NextFrameToRender->Flags.bSplashIsShown) + { + NextFrameNumber++; + } + + FSettingsPtr XSettings = Settings->Clone(); + FGameFramePtr XFrame = NextFrameToRender->Clone(); + TArray<FLayerPtr> XLayers; + + XLayers.Empty(LayerMap.Num()); + + for (auto Pair : LayerMap) + { + XLayers.Emplace(Pair.Value->Clone()); + } + + XLayers.Sort(FLayerPtr_CompareId()); + + ExecuteOnRenderThread_DoNotWait([this, XSettings, XFrame, XLayers](FRHICommandListImmediate& RHICmdList) + { + if (XFrame.IsValid()) + { + Settings_RenderThread = XSettings; + Frame_RenderThread = XFrame; + + int32 XLayerIndex = 0; + int32 LayerIndex_RenderThread = 0; + TArray<FLayerPtr> ValidXLayers; + + while (XLayerIndex < XLayers.Num() && LayerIndex_RenderThread < Layers_RenderThread.Num()) + { + uint32 LayerIdA = XLayers[XLayerIndex]->GetId(); + uint32 LayerIdB = Layers_RenderThread[LayerIndex_RenderThread]->GetId(); + + if (LayerIdA < LayerIdB) + { + if (XLayers[XLayerIndex]->Initialize_RenderThread(Settings_RenderThread.Get(), CustomPresent, &DeferredDeletion, RHICmdList)) + { + ValidXLayers.Add(XLayers[XLayerIndex]); + } + XLayerIndex++; + } + else if (LayerIdA > LayerIdB) + { + DeferredDeletion.AddLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]); + } + else + { + if(XLayers[XLayerIndex]->Initialize_RenderThread(Settings_RenderThread.Get(), CustomPresent, &DeferredDeletion, RHICmdList, Layers_RenderThread[LayerIndex_RenderThread].Get())) + { + LayerIndex_RenderThread++; + ValidXLayers.Add(XLayers[XLayerIndex]); + } + XLayerIndex++; + } + } + + while (XLayerIndex < XLayers.Num()) + { + if(XLayers[XLayerIndex]->Initialize_RenderThread(Settings_RenderThread.Get(), CustomPresent, &DeferredDeletion, RHICmdList)) + { + ValidXLayers.Add(XLayers[XLayerIndex]); + } + XLayerIndex++; + } + + while (LayerIndex_RenderThread < Layers_RenderThread.Num()) + { + DeferredDeletion.AddLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]); + } + + Layers_RenderThread = ValidXLayers; + + DeferredDeletion.HandleLayerDeferredDeletionQueue_RenderThread(); + } + }); + } + } + + void FOculusXRHMD::FinishRenderFrame_RenderThread(FRDGBuilder& GraphBuilder) + { + CheckInRenderThread(); + + if (Frame_RenderThread.IsValid()) + { + UE_LOG(LogHMD, VeryVerbose, TEXT("FinishRenderFrame %u"), Frame_RenderThread->FrameNumber); + + AddPass(GraphBuilder, RDG_EVENT_NAME("FinishRenderFrame"), [this](FRHICommandListImmediate& RHICmdList) + { + if (Frame_RenderThread->ShowFlags.Rendering) + { + for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++) + { + Layers_RenderThread[LayerIndex]->UpdateTexture_RenderThread(CustomPresent, RHICmdList); + Layers_RenderThread[LayerIndex]->UpdatePassthrough_RenderThread(CustomPresent, RHICmdList, Frame_RenderThread.Get()); + } + } + Frame_RenderThread.Reset(); + }); + } + + } + + + void FOculusXRHMD::StartRHIFrame_RenderThread() + { + CheckInRenderThread(); + + if (Frame_RenderThread.IsValid()) + { + UE_LOG(LogHMD, VeryVerbose, TEXT("StartRHIFrame %u"), Frame_RenderThread->FrameNumber); + + FSettingsPtr XSettings = Settings_RenderThread->Clone(); + FGameFramePtr XFrame = Frame_RenderThread->Clone(); + TArray<FLayerPtr> XLayers = Layers_RenderThread; + + for (int32 XLayerIndex = 0; XLayerIndex < XLayers.Num(); XLayerIndex++) + { + XLayers[XLayerIndex] = XLayers[XLayerIndex]->Clone(); + } + + ExecuteOnRHIThread_DoNotWait([this, XSettings, XFrame, XLayers]() + { + if (XFrame.IsValid()) + { + Settings_RHIThread = XSettings; + Frame_RHIThread = XFrame; + Layers_RHIThread = XLayers; + + ovrpXrApi NativeXrApi; + FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi); + if ((Frame_RHIThread->ShowFlags.Rendering || NativeXrApi == ovrpXrApi_OpenXR) && !Frame_RHIThread->Flags.bSplashIsShown) + { + SCOPED_NAMED_EVENT(BeginFrame, FColor::Red); + + UE_LOG(LogHMD, Verbose, TEXT("FOculusXRHMDModule::GetPluginWrapper().BeginFrame4 %u"), Frame_RHIThread->FrameNumber); + + ovrpResult Result; + if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().BeginFrame4(Frame_RHIThread->FrameNumber, CustomPresent->GetOvrpCommandQueue()))) + { + UE_LOG(LogHMD, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().BeginFrame4 %u failed (%d)"), Frame_RHIThread->FrameNumber, Result); + Frame_RHIThread->ShowFlags.Rendering = false; + } + else + { +#if PLATFORM_ANDROID + FOculusXRHMDModule::GetPluginWrapper().SetTiledMultiResLevel((ovrpTiledMultiResLevel)Frame_RHIThread->FoveatedRenderingLevel); + FOculusXRHMDModule::GetPluginWrapper().SetTiledMultiResDynamic(Frame_RHIThread->bDynamicFoveatedRendering ? ovrpBool_True : ovrpBool_False); +#ifdef WITH_OCULUS_BRANCH + // If we're using eye tracked foveated rendering, set that at the end of the render pass instead (through UpdateFoveationOffsets_RenderThread) + if (Frame_RHIThread->FoveatedRenderingMethod != EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering) + { + FOculusXRHMDModule::GetPluginWrapper().SetFoveationEyeTracked(ovrpBool_False); + // Need to also not use offsets when turning off eye tracked foveated rendering + if (CustomPresent) + { + CustomPresent->UpdateFoveationOffsets_RHIThread(false, nullptr); + } + } +#endif // WITH_OCULUS_BRANCH +#endif // PLATFORM_ANDROID + } + } + } + }); + } + } + + + void FOculusXRHMD::FinishRHIFrame_RHIThread() + { + CheckInRHIThread(); + + if (Frame_RHIThread.IsValid()) + { + UE_LOG(LogHMD, VeryVerbose, TEXT("FinishRHIFrame %u"), Frame_RHIThread->FrameNumber); + + ovrpXrApi NativeXrApi; + FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi); + if ((Frame_RHIThread->ShowFlags.Rendering || NativeXrApi == ovrpXrApi_OpenXR) && !Frame_RHIThread->Flags.bSplashIsShown) + { + SCOPED_NAMED_EVENT(EndFrame, FColor::Red); + + TArray<FLayerPtr> Layers = Layers_RHIThread; + Layers.Sort(FLayerPtr_CompareTotal()); + TArray<const ovrpLayerSubmit*> LayerSubmitPtr; + + int32 LayerNum = Layers.Num(); + + LayerSubmitPtr.SetNum(LayerNum); + + int32 FinalLayerNumber = 0; + for (int32 LayerIndex = 0; LayerIndex < LayerNum; LayerIndex++) + { + if (Layers[LayerIndex]->IsVisible()) + { + LayerSubmitPtr[FinalLayerNumber++] = Layers[LayerIndex]->UpdateLayer_RHIThread(Settings_RHIThread.Get(), Frame_RHIThread.Get(), LayerIndex); + } + } + + UE_LOG(LogHMD, Verbose, TEXT("FOculusXRHMDModule::GetPluginWrapper().EndFrame4 %u"), Frame_RHIThread->FrameNumber); + FOculusXRHMDModule::GetPluginWrapper().SetEyeFovPremultipliedAlphaMode(false); + + ovrpResult Result; + if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().EndFrame4(Frame_RHIThread->FrameNumber, LayerSubmitPtr.GetData(), FinalLayerNumber, CustomPresent->GetOvrpCommandQueue()))) + { + UE_LOG(LogHMD, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().EndFrame4 %u failed (%d)"), Frame_RHIThread->FrameNumber, Result); + } + else + { + for (int32 LayerIndex = 0; LayerIndex < Layers.Num(); LayerIndex++) + { + Layers[LayerIndex]->IncrementSwapChainIndex_RHIThread(CustomPresent); + } + } + } + } + + Frame_RHIThread.Reset(); + } + + void FOculusXRHMD::AddEventPollingDelegate(const FOculusXRHMDEventPollingDelegate& NewDelegate) + { + EventPollingDelegates.Add(NewDelegate); + } + + /// @cond DOXYGEN_WARNINGS + +#define BOOLEAN_COMMAND_HANDLER_BODY(ConsoleName, FieldExpr)\ + do\ + {\ + if (Args.Num()) \ + {\ + if (Args[0].Equals(TEXT("toggle"), ESearchCase::IgnoreCase))\ + {\ + (FieldExpr) = !(FieldExpr);\ + }\ + else\ + {\ + (FieldExpr) = FCString::ToBool(*Args[0]);\ + }\ + }\ + Ar.Logf(ConsoleName TEXT(" = %s"), (FieldExpr) ? TEXT("On") : TEXT("Off"));\ + }\ + while(false) + + + void FOculusXRHMD::UpdateOnRenderThreadCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) + { + CheckInGameThread(); + + BOOLEAN_COMMAND_HANDLER_BODY(TEXT("vr.oculus.bUpdateOnRenderThread"), Settings->Flags.bUpdateOnRT); + } + + + void FOculusXRHMD::PixelDensityMinCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar) + { + CheckInGameThread(); + + if (Args.Num()) + { + Settings->SetPixelDensityMin(FCString::Atof(*Args[0])); + } + Ar.Logf(TEXT("vr.oculus.PixelDensity.min = \"%1.2f\""), Settings->PixelDensityMin); + } + + + void FOculusXRHMD::PixelDensityMaxCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar) + { + CheckInGameThread(); + + if (Args.Num()) + { + Settings->SetPixelDensityMax(FCString::Atof(*Args[0])); + } + Ar.Logf(TEXT("vr.oculus.PixelDensity.max = \"%1.2f\""), Settings->PixelDensityMax); + } + + + void FOculusXRHMD::HQBufferCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar) + { + CheckInGameThread(); + + BOOLEAN_COMMAND_HANDLER_BODY(TEXT("vr.oculus.bHQBuffer"), Settings->Flags.bHQBuffer); + } + + + void FOculusXRHMD::HQDistortionCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar) + { + CheckInGameThread(); + + BOOLEAN_COMMAND_HANDLER_BODY(TEXT("vr.oculus.bHQDistortion"), Settings->Flags.bHQDistortion); + } + + void FOculusXRHMD::ShowGlobalMenuCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) + { + CheckInGameThread(); + + if (!OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().ShowSystemUI2(ovrpUI::ovrpUI_GlobalMenu))) + { + Ar.Logf(TEXT("Could not show platform menu")); + } + } + + void FOculusXRHMD::ShowQuitMenuCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) + { + CheckInGameThread(); + + if (!OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().ShowSystemUI2(ovrpUI::ovrpUI_ConfirmQuit))) + { + Ar.Logf(TEXT("Could not show platform menu")); + } + } + +#if !UE_BUILD_SHIPPING + void FOculusXRHMD::StatsCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar) + { + CheckInGameThread(); + + BOOLEAN_COMMAND_HANDLER_BODY(TEXT("vr.oculus.Debug.bShowStats"), Settings->Flags.bShowStats); + } + + + void FOculusXRHMD::ShowSettingsCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) + { + Ar.Logf(TEXT("stereo ipd=%.4f\n nearPlane=%.4f"), GetInterpupillaryDistance(), GNearClippingPlane); + } + + + void FOculusXRHMD::IPDCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) + { + if (Args.Num() > 0) + { + SetInterpupillaryDistance(FCString::Atof(*Args[0])); + } + Ar.Logf(TEXT("vr.oculus.Debug.IPD = %f"), GetInterpupillaryDistance()); + } + +#endif // !UE_BUILD_SHIPPING + + void FOculusXRHMD::LoadFromSettings() + { + UOculusXRHMDRuntimeSettings* HMDSettings = GetMutableDefault<UOculusXRHMDRuntimeSettings>(); + check(HMDSettings); + + Settings->Flags.bSupportsDash = HMDSettings->bSupportsDash; + Settings->Flags.bCompositeDepth = HMDSettings->bCompositesDepth; + Settings->Flags.bHQDistortion = HMDSettings->bHQDistortion; + Settings->Flags.bInsightPassthroughEnabled = HMDSettings->bInsightPassthroughEnabled; + Settings->SuggestedCpuPerfLevel = HMDSettings->SuggestedCpuPerfLevel; + Settings->SuggestedGpuPerfLevel = HMDSettings->SuggestedGpuPerfLevel; + Settings->FoveatedRenderingMethod = HMDSettings->FoveatedRenderingMethod; + Settings->FoveatedRenderingLevel = HMDSettings->FoveatedRenderingLevel; + Settings->bDynamicFoveatedRendering = HMDSettings->bDynamicFoveatedRendering; + Settings->PixelDensityMin = HMDSettings->PixelDensityMin; + Settings->PixelDensityMax = HMDSettings->PixelDensityMax; + Settings->ColorSpace = HMDSettings->ColorSpace; + Settings->ControllerPoseAlignment = HMDSettings->ControllerPoseAlignment; + Settings->bLateLatching = HMDSettings->bLateLatching; + Settings->XrApi = HMDSettings->XrApi; + Settings->bSupportExperimentalFeatures = HMDSettings->bSupportExperimentalFeatures; + Settings->bSupportEyeTrackedFoveatedRendering = HMDSettings->bSupportEyeTrackedFoveatedRendering; + } + + /// @endcond + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD.h new file mode 100644 index 0000000000000000000000000000000000000000..cea3dcce5fea8131db2bec0989f45599eca1f7ec --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD.h @@ -0,0 +1,514 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDModule.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD_Settings.h" +#include "OculusXRHMD_GameFrame.h" +#include "OculusXRHMD_CustomPresent.h" +#include "OculusXRHMD_Layer.h" +#include "OculusXRHMD_Splash.h" +#include "OculusXRHMD_StressTester.h" +#include "OculusXRHMD_ConsoleCommands.h" +#include "OculusXRHMD_SpectatorScreenController.h" +#include "OculusXRHMD_DynamicResolutionState.h" +#include "OculusXRHMD_DeferredDeletionQueue.h" + +#include "OculusXRAssetManager.h" + +#include "HeadMountedDisplayBase.h" +#include "HeadMountedDisplay.h" +#include "XRRenderTargetManager.h" +#include "XRRenderBridge.h" +#include "IStereoLayers.h" +#include "Stats/Stats.h" +#include "SceneViewExtension.h" +#include "Engine/Engine.h" +#include "Engine/StaticMeshActor.h" +#include "XRThreadUtils.h" +#include "ProceduralMeshComponent.h" + + +namespace OculusXRHMD +{ + +DECLARE_DELEGATE_TwoParams(FOculusXRHMDEventPollingDelegate, ovrpEventDataBuffer*, bool&); + +//------------------------------------------------------------------------------------------------- +// FPerformanceStats +//------------------------------------------------------------------------------------------------- + +struct FPerformanceStats +{ + uint64 Frames; + double Seconds; + + FPerformanceStats(uint32 InFrames = 0, double InSeconds = 0.0) + : Frames(InFrames) + , Seconds(InSeconds) + {} + + FPerformanceStats operator - (const FPerformanceStats& PerformanceStats) const + { + return FPerformanceStats( + Frames - PerformanceStats.Frames, + Seconds - PerformanceStats.Seconds); + } +}; + +enum FRecenterTypes +{ + RecenterOrientation = 0x1, + RecenterPosition = 0x2, + RecenterOrientationAndPosition = 0x3 +}; + +//------------------------------------------------------------------------------------------------- +// FOculusXRHMD - Oculus Rift Head Mounted Display +//------------------------------------------------------------------------------------------------- + +class FOculusXRHMD : public FHeadMountedDisplayBase, public FXRRenderTargetManager, public IStereoLayers, public FHMDSceneViewExtension, public FOculusAssetManager +{ + friend class UOculusXRFunctionLibrary; + friend FOculusXRHMDModule; + friend class FSplash; + friend class FConsoleCommands; + +public: + OCULUSXRHMD_API static const FName OculusSystemName; + // IXRSystemIdentifier + virtual FName GetSystemName() const override; + virtual int32 GetXRSystemFlags() const override; + + // IXRTrackingSystem + virtual FString GetVersionString() const override; + virtual bool DoesSupportPositionalTracking() const override; + virtual bool HasValidTrackingPosition() override; + virtual bool EnumerateTrackedDevices(TArray<int32>& OutDevices, EXRTrackedDeviceType Type=EXRTrackedDeviceType::Any) override; + virtual bool GetCurrentPose(int32 InDeviceId, FQuat& OutOrientation, FVector& OutPosition) override; + virtual bool GetRelativeEyePose(int32 InDeviceId, int32 ViewIndex, FQuat& OutOrientation, FVector& OutPosition) override; + virtual bool GetTrackingSensorProperties(int32 InDeviceId, FQuat& OutOrientation, FVector& OutPosition, FXRSensorProperties& OutSensorProperties) override; + virtual void SetTrackingOrigin(EHMDTrackingOrigin::Type NewOrigin) override; + virtual EHMDTrackingOrigin::Type GetTrackingOrigin() const override; + virtual bool GetFloorToEyeTrackingTransform(FTransform& OutFloorToEye) const override; + //virtual FVector GetAudioListenerOffset(int32 InDeviceId = HMDDeviceId) const override; + virtual void ResetOrientationAndPosition(float Yaw = 0.f) override; + virtual void ResetOrientation(float Yaw = 0.f) override; + virtual void ResetPosition() override; + virtual void SetBaseRotation(const FRotator& BaseRot) override; + virtual FRotator GetBaseRotation() const override; + virtual void SetBaseOrientation(const FQuat& BaseOrient) override; + virtual FQuat GetBaseOrientation() const override; + //virtual TSharedPtr<class IXRCamera, ESPMode::ThreadSafe> GetXRCamera(int32 DeviceId = HMDDeviceId) override; + virtual class IHeadMountedDisplay* GetHMDDevice() override { return this; } + virtual class TSharedPtr< class IStereoRendering, ESPMode::ThreadSafe > GetStereoRenderingDevice() override { return SharedThis(this); } + //virtual class IXRInput* GetXRInput() override; + virtual bool IsHeadTrackingEnforced() const override; + virtual void SetHeadTrackingEnforced(bool bEnabled) override; + virtual bool IsHeadTrackingAllowed() const override; + virtual void OnBeginPlay(FWorldContext& InWorldContext) override; + virtual void OnEndPlay(FWorldContext& InWorldContext) override; + virtual bool OnStartGameFrame(FWorldContext& WorldContext) override; + virtual bool OnEndGameFrame(FWorldContext& WorldContext) override; + virtual void OnBeginRendering_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily) override; + virtual class IXRLoadingScreen* CreateLoadingScreen() override { return GetSplash(); } + virtual FVector2D GetPlayAreaBounds(EHMDTrackingOrigin::Type Origin) const override; + + // IHeadMountedDisplay + virtual bool IsHMDConnected() override { return true; } + virtual bool IsHMDEnabled() const override; + virtual EHMDWornState::Type GetHMDWornState() override; + virtual void EnableHMD(bool bEnable = true) override; + virtual bool GetHMDMonitorInfo(MonitorInfo&) override; + virtual void GetFieldOfView(float& InOutHFOVInDegrees, float& InOutVFOVInDegrees) const override; + virtual void SetInterpupillaryDistance(float NewInterpupillaryDistance) override; + virtual float GetInterpupillaryDistance() const override; + //virtual void SetClippingPlanes(float NCP, float FCP) override; + //virtual FVector GetAudioListenerOffset() const override; + virtual bool GetHMDDistortionEnabled(EShadingPath ShadingPath) const override; + //virtual void BeginRendering_RenderThread(const FTransform& NewRelativeTransform, FRHICommandListImmediate& RHICmdList, FSceneViewFamily& ViewFamily) override; + //virtual bool IsSpectatorScreenActive() const override; + //virtual class ISpectatorScreenController* GetSpectatorScreenController() override; + //virtual class ISpectatorScreenController const* GetSpectatorScreenController() const override; + //virtual float GetDistortionScalingFactor() const override; + //virtual float GetLensCenterOffset() const override; + //virtual void GetDistortionWarpValues(FVector4& K) const override; + virtual bool IsChromaAbCorrectionEnabled() const override; + //virtual bool GetChromaAbCorrectionValues(FVector4& K) const override; + virtual bool HasHiddenAreaMesh() const override; + virtual bool HasVisibleAreaMesh() const override; + virtual void DrawHiddenAreaMesh(class FRHICommandList& RHICmdList, int32 ViewIndex) const override; + virtual void DrawVisibleAreaMesh(class FRHICommandList& RHICmdList, int32 ViewIndex) const override; + //virtual void DrawDistortionMesh_RenderThread(struct FHeadMountedDisplayPassContext& Context, const FIntPoint& TextureSize) override; + //virtual void UpdateScreenSettings(const FViewport* InViewport) override; + //virtual void UpdatePostProcessSettings(FPostProcessSettings*) override; + //virtual FTexture* GetDistortionTextureLeft() const override; + //virtual FTexture* GetDistortionTextureRight() const override; + //virtual FVector2D GetTextureOffsetLeft() const override; + //virtual FVector2D GetTextureOffsetRight() const override; + //virtual FVector2D GetTextureScaleLeft() const override; + //virtual FVector2D GetTextureScaleRight() const override; + //virtual const float* GetRedDistortionParameters() const override; + //virtual const float* GetGreenDistortionParameters() const override; + //virtual const float* GetBlueDistortionParameters() const override; + //virtual bool NeedsUpscalePostProcessPass() override; + //virtual void RecordAnalytics() override; + //virtual bool DoesAppUseVRFocus() const override; + //virtual bool DoesAppHaveVRFocus() const override; + virtual float GetPixelDenity() const override; + virtual void SetPixelDensity(const float NewPixelDensity) override; + virtual FIntPoint GetIdealRenderTargetSize() const override; + + // IStereoRendering interface + virtual bool IsStereoEnabled() const override; + virtual bool IsStereoEnabledOnNextFrame() const override; + virtual bool EnableStereo(bool stereo = true) override; + virtual void AdjustViewRect(int32 ViewIndex, int32& X, int32& Y, uint32& SizeX, uint32& SizeY) const override; + virtual void SetFinalViewRect(FRHICommandListImmediate& RHICmdList, const int32 ViewIndex, const FIntRect& FinalViewRect) override; + //virtual FVector2D GetTextSafeRegionBounds() const override; + virtual void CalculateStereoViewOffset(const int32 ViewIndex, FRotator& ViewRotation, const float WorldToMeters, FVector& ViewLocation) override; + virtual FMatrix GetStereoProjectionMatrix(const int32 ViewIndex) const override; + virtual void InitCanvasFromView(class FSceneView* InView, class UCanvas* Canvas) override; + //virtual void GetEyeRenderParams_RenderThread(const struct FRenderingCompositePassContext& Context, FVector2D& EyeToSrcUVScaleValue, FVector2D& EyeToSrcUVOffsetValue) const override; + virtual void RenderTexture_RenderThread(class FRHICommandListImmediate& RHICmdList, class FRHITexture* BackBuffer, class FRHITexture* SrcTexture, FVector2D WindowSize) const override; + //virtual void SetClippingPlanes(float NCP, float FCP) override; + virtual IStereoRenderTargetManager* GetRenderTargetManager() override { return this; } + virtual IStereoLayers* GetStereoLayers() override { return this; } + //virtual void UseImplicitHmdPosition(bool bInImplicitHmdPosition) override; + //virtual bool GetUseImplicitHmdPosition() override; + virtual bool IsStandaloneStereoOnlyDevice() const override { return bIsStandaloneStereoOnlyDevice; } + bool SupportsSpaceWarp() const; + + // FHeadMountedDisplayBase interface + virtual FVector2D GetEyeCenterPoint_RenderThread(int32 ViewIndex) const override; + virtual FIntRect GetFullFlatEyeRect_RenderThread(FTexture2DRHIRef EyeTexture) const override; + virtual void CopyTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture2D* SrcTexture, FIntRect SrcRect, FRHITexture2D* DstTexture, FIntRect DstRect, bool bClearBlack, bool bNoAlpha) const override; + virtual bool PopulateAnalyticsAttributes(TArray<struct FAnalyticsEventAttribute>& EventAttributes) override; + + // FXRRenderTargetManager interface + virtual bool ShouldUseSeparateRenderTarget() const override; + virtual void CalculateRenderTargetSize(const FViewport& Viewport, uint32& InOutSizeX, uint32& InOutSizeY) override; + virtual bool NeedReAllocateViewportRenderTarget(const class FViewport& Viewport) override; + virtual bool NeedReAllocateDepthTexture(const TRefCountPtr<IPooledRenderTarget>& DepthTarget) override; + virtual bool NeedReAllocateShadingRateTexture(const TRefCountPtr<IPooledRenderTarget>& FoveationTarget) override; +#ifdef WITH_OCULUS_BRANCH + virtual bool NeedReAllocateMotionVectorTexture(const TRefCountPtr<IPooledRenderTarget>& MotionVectorTarget, const TRefCountPtr<IPooledRenderTarget>& MotionVectorDepthTarget) override; +#endif // WITH_OCULUS_BRANCH + virtual bool AllocateRenderTargetTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples = 1) override; + virtual bool AllocateDepthTexture(uint32 Index, uint32 SizeX, uint32 SizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags TargetableTextureFlags, FTexture2DRHIRef& OutTargetableTexture, FTexture2DRHIRef& OutShaderResourceTexture, uint32 NumSamples = 1) override; + virtual bool AllocateShadingRateTexture(uint32 Index, uint32 RenderSizeX, uint32 RenderSizeY, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTexture, FIntPoint& OutTextureSize) override; +#ifdef WITH_OCULUS_BRANCH + virtual bool AllocateMotionVectorTexture(uint32 Index, uint8 Format, uint32 NumMips, ETextureCreateFlags InTexFlags, ETextureCreateFlags InTargetableTextureFlags, FTexture2DRHIRef& OutTexture, FIntPoint& OutTextureSize, FTexture2DRHIRef& OutDepthTexture, FIntPoint& OutDepthTextureSize) override; +#endif // WITH_OCULUS_BRANCH + virtual void UpdateViewportWidget(bool bUseSeparateRenderTarget, const class FViewport& Viewport, class SViewport* ViewportWidget) override; + virtual FXRRenderBridge* GetActiveRenderBridge_GameThread(bool bUseSeparateRenderTarget); + void AllocateEyeBuffer(); + + // IStereoLayers interface + virtual uint32 CreateLayer(const IStereoLayers::FLayerDesc& InLayerDesc) override; + virtual void DestroyLayer(uint32 LayerId) override; + virtual void SetLayerDesc(uint32 LayerId, const IStereoLayers::FLayerDesc& InLayerDesc) override; + virtual bool GetLayerDesc(uint32 LayerId, IStereoLayers::FLayerDesc& OutLayerDesc) override; + virtual void MarkTextureForUpdate(uint32 LayerId) override; + virtual IStereoLayers::FLayerDesc GetDebugCanvasLayerDesc(FTextureRHIRef Texture) override; + virtual void GetAllocatedTexture(uint32 LayerId, FTextureRHIRef &Texture, FTextureRHIRef &LeftTexture) override; + virtual bool ShouldCopyDebugLayersToSpectatorScreen() const override { return true; } + virtual void PushLayerState(bool) override { /* Todo */ } + virtual void PopLayerState() override { /* Todo */ } + + // ISceneViewExtension + virtual void SetupViewFamily(FSceneViewFamily& InViewFamily) override; + virtual void SetupView(FSceneViewFamily& InViewFamily, FSceneView& InView) override; + virtual void BeginRenderViewFamily(FSceneViewFamily& InViewFamily) override; + virtual void PreRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily) override; + virtual void PreRenderView_RenderThread(FRDGBuilder& GraphBuilder, FSceneView& InView) override; + virtual void PostRenderViewFamily_RenderThread(FRDGBuilder& GraphBuilder, FSceneViewFamily& InViewFamily) override; + virtual void PostRenderBasePassMobile_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneView& InView) override; + virtual int32 GetPriority() const override; + virtual bool IsActiveThisFrame_Internal(const FSceneViewExtensionContext& Context) const override; +#ifdef WITH_OCULUS_LATE_LATCHING + virtual bool LateLatchingEnabled() const override; + virtual void PreLateLatchingViewFamily_RenderThread(FRHICommandListImmediate& RHICmdList, FSceneViewFamily& InViewFamily) override; +#endif + +public: + FOculusXRHMD(const FAutoRegister&); + ~FOculusXRHMD(); + +protected: + bool Startup(); + void PreShutdown(); + void Shutdown(); + bool InitializeSession(); + void ShutdownSession(); + bool InitDevice(); + void ReleaseDevice(); + void ApplicationPauseDelegate(); + void ApplicationResumeDelegate(); + bool CheckEyeTrackingPermission(EOculusXRFoveatedRenderingMethod FoveatedRenderingMethod); + void SetupOcclusionMeshes(); + void UpdateStereoRenderingParams(); + void UpdateHmdRenderInfo(); + void InitializeEyeLayer_RenderThread(FRHICommandListImmediate& RHICmdList); + void ApplySystemOverridesOnStereo(bool force = false); + bool OnOculusStateChange(bool bIsEnabledNow); + bool ShouldDisableHiddenAndVisibileAreaMeshForSpectatorScreen_RenderThread() const; + void Recenter(FRecenterTypes RecenterType, float Yaw); +#if !UE_BUILD_SHIPPING + void DrawDebug(UCanvas* InCanvas, APlayerController* InPlayerController); +#endif + + class FSceneViewport* FindSceneViewport(); + FOculusXRSplashDesc GetUESplashScreenDesc(); + void EyeTrackedFoveatedRenderingFallback(); + +public: + + OCULUSXRHMD_API static FOculusXRHMD* GetOculusXRHMD(); + + bool IsHMDActive() const; + + FSplash* GetSplash() const { return Splash.Get(); } + FCustomPresent* GetCustomPresent_Internal() const { return CustomPresent; } + + float GetWorldToMetersScale() const; + + ESpectatorScreenMode GetSpectatorScreenMode_RenderThread() const; + + FVector GetNeckPosition(const FQuat& HeadOrientation, const FVector& HeadPosition); + + /** + * Sets base position offset (in meters). The base position offset is the distance from the physical (0, 0, 0) position + * to current HMD position (bringing the (0, 0, 0) point to the current HMD position) + * Note, this vector is set by ResetPosition call; use this method with care. + * The axis of the vector are the same as in Unreal: X - forward, Y - right, Z - up. + * + * @param BaseOffset (in) the vector to be set as base offset, in meters. + */ + void SetBaseOffsetInMeters(const FVector& BaseOffset); + + /** + * Returns the currently used base position offset, previously set by the + * ResetPosition or SetBasePositionOffset calls. It represents a vector that translates the HMD's position + * into (0,0,0) point, in meters. + * The axis of the vector are the same as in Unreal: X - forward, Y - right, Z - up. + * + * @return Base position offset, in meters. + */ + FVector GetBaseOffsetInMeters() const; + + OCULUSXRHMD_API bool ConvertPose(const ovrpPosef& InPose, FPose& OutPose) const; + OCULUSXRHMD_API bool ConvertPose(const FPose& InPose, ovrpPosef& OutPose) const; + OCULUSXRHMD_API bool ConvertPose_RenderThread(const ovrpPosef& InPose, FPose& OutPose) const; + OCULUSXRHMD_API static bool ConvertPose_Internal(const ovrpPosef& InPose, FPose& OutPose, const FSettings* Settings, float WorldToMetersScale = 100.0f); + OCULUSXRHMD_API static bool ConvertPose_Internal(const FPose& InPose, ovrpPosef& OutPose, const FSettings* Settings, float WorldToMetersScale = 100.0f); + + /** Turns ovrVector3f in Unreal World space to a scaled FVector and applies translation and rotation corresponding to player movement */ + FVector ScaleAndMovePointWithPlayer(ovrpVector3f& OculusXRHMDPoint); + + /** The inverse of ScaleAndMovePointWithPlayer */ + ovrpVector3f WorldLocationToOculusPoint(const FVector& InUnrealPosition); + + /** Convert dimension of a float (e.g., a distance) from meters to Unreal Units */ + float ConvertFloat_M2U(float OculusFloat) const; + FVector ConvertVector_M2U(ovrpVector3f OculusPoint) const; + + struct UserProfile + { + float IPD; + float EyeDepth; + float EyeHeight; + }; + + bool GetUserProfile(UserProfile& OutProfile); + float GetVsyncToNextVsync() const; + FPerformanceStats GetPerformanceStats() const; + bool DoEnableStereo(bool bStereo); + void ResetControlRotation() const; + void UpdateFoveationOffsets_RenderThread(); + + FSettingsPtr CreateNewSettings() const; + FGameFramePtr CreateNewGameFrame() const; + + FGameFrame* GetFrame() { CheckInGameThread(); return Frame.Get(); } + const FGameFrame* GetFrame() const { CheckInGameThread(); return Frame.Get(); } + FGameFrame* GetFrame_RenderThread() { CheckInRenderThread(); return Frame_RenderThread.Get(); } + const FGameFrame* GetFrame_RenderThread() const { CheckInRenderThread(); return Frame_RenderThread.Get(); } + FGameFrame* GetFrame_RHIThread() { CheckInRHIThread(); return Frame_RHIThread.Get(); } + const FGameFrame* GetFrame_RHIThread() const { CheckInRHIThread(); return Frame_RHIThread.Get(); } + FGameFrame* GetNextFrameToRender() { CheckInGameThread(); return NextFrameToRender.Get(); } + const FGameFrame* GetNextFrameToRender() const { CheckInGameThread(); return NextFrameToRender.Get(); } + + FSettings* GetSettings() { CheckInGameThread(); return Settings.Get(); } + const FSettings* GetSettings() const { CheckInGameThread(); return Settings.Get(); } + FSettings* GetSettings_RenderThread() { CheckInRenderThread(); return Settings_RenderThread.Get(); } + const FSettings* GetSettings_RenderThread() const { CheckInRenderThread(); return Settings_RenderThread.Get(); } + FSettings* GetSettings_RHIThread() { CheckInRHIThread(); return Settings_RHIThread.Get(); } + const FSettings* GetSettings_RHIThread() const { CheckInRHIThread(); return Settings_RHIThread.Get(); } + + const int GetNextFrameNumber() const { return NextFrameNumber; } + + const FRotator GetSplashRotation() const { return SplashRotation; } + void SetSplashRotationToForward(); + + OCULUSXRHMD_API void StartGameFrame_GameThread(); // Called from OnStartGameFrame or from FOculusXRInput::SendControllerEvents (first actual call of the frame) + void FinishGameFrame_GameThread(); // Called from OnEndGameFrame + void StartRenderFrame_GameThread(); // Called from BeginRenderViewFamily + void FinishRenderFrame_RenderThread(FRDGBuilder& GraphBuilder); // Called from PostRenderViewFamily_RenderThread + void StartRHIFrame_RenderThread(); // Called from PreRenderViewFamily_RenderThread + void FinishRHIFrame_RHIThread(); // Called from FinishRendering_RHIThread + + void GetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel& CpuPerfLevel, EOculusXRProcessorPerformanceLevel& GpuPerfLevel); + void SetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel CpuPerfLevel, EOculusXRProcessorPerformanceLevel GpuPerfLevel); + void SetFoveatedRenderingMethod(EOculusXRFoveatedRenderingMethod InFoveationMethod); + void SetFoveatedRenderingLevel(EOculusXRFoveatedRenderingLevel InFoveationLevel, bool isDynamic); + void SetColorScaleAndOffset(FLinearColor ColorScale, FLinearColor ColorOffset, bool bApplyToAllLayers); + + OCULUSXRHMD_API void UpdateRTPoses(); + + FTransform GetLastTrackingToWorld() const { return LastTrackingToWorld; } + OCULUSXRHMD_API void AddEventPollingDelegate(const FOculusXRHMDEventPollingDelegate& NewDelegate); + +protected: + FConsoleCommands ConsoleCommands; + void UpdateOnRenderThreadCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); + void PixelDensityMinCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); + void PixelDensityMaxCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); + void HQBufferCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar); + void HQDistortionCommandHandler(const TArray<FString>& Args, UWorld*, FOutputDevice& Ar); + void ShowGlobalMenuCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); + void ShowQuitMenuCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); +#if !UE_BUILD_SHIPPING + void StatsCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); + void ShowSettingsCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); + void IPDCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); +#endif + + void LoadFromSettings(); + void DoSessionShutdown(); + +protected: + void UpdateHMDWornState(); + EHMDWornState::Type HMDWornState = EHMDWornState::Unknown; + + void UpdateHMDEvents(); + + void UpdateInsightPassthrough(); + void ShutdownInsightPassthrough(); + + union + { + struct + { + uint64 bApplySystemOverridesOnStereo : 1; + + uint64 bNeedEnableStereo : 1; + uint64 bNeedDisableStereo : 1; + }; + uint64 Raw; + } Flags; + + union + { + struct + { + // set to true when origin was set while OvrSession == null; the origin will be set ASA OvrSession != null + uint64 NeedSetTrackingOrigin : 1; + // enforces exit; used mostly for testing + uint64 EnforceExit : 1; + // set if a game is paused by the plug-in + uint64 AppIsPaused : 1; + // set to indicate that DisplayLost was detected by game thread. + uint64 DisplayLostDetected : 1; + // set to true once new session is created; being handled and reset as soon as session->IsVisible. + uint64 NeedSetFocusToGameViewport : 1; + }; + uint64 Raw; + } OCFlags; + + TRefCountPtr<FCustomPresent> CustomPresent; + FSplashPtr Splash; + IRendererModule* RendererModule; + + FDeferredDeletionQueue DeferredDeletion; + + EHMDTrackingOrigin::Type TrackingOrigin; + // Stores difference between ViewRotation and EyeOrientation from previous frame + FQuat LastPlayerOrientation; + // Stores GetFrame()->PlayerLocation (i.e., ViewLocation) from the previous frame + FVector LastPlayerLocation; + FRotator DeltaControlRotation; // used from ApplyHmdRotation + TWeakPtr<SWidget> CachedViewportWidget; + TWeakPtr<SWindow> CachedWindow; + FVector2D CachedWindowSize; + float CachedWorldToMetersScale; + bool bIsStandaloneStereoOnlyDevice; + // Stores TrackingToWorld from previous frame + FTransform LastTrackingToWorld; + + // These three properties indicate the current state of foveated rendering, which may differ from what's in Settings + // due to cases such as falling back to FFR when eye tracked foveated rendering isn't enabled. Will allow us to resume + // ETFR from situations such as when ET gets paused. + std::atomic<EOculusXRFoveatedRenderingMethod> FoveatedRenderingMethod; + std::atomic<EOculusXRFoveatedRenderingLevel> FoveatedRenderingLevel; + std::atomic<bool> bDynamicFoveatedRendering; + + // Game thread + FSettingsPtr Settings; + uint32 NextFrameNumber; + uint32 WaitFrameNumber; + FGameFramePtr Frame; // Valid from OnStartGameFrame to OnEndGameFrame + FGameFramePtr NextFrameToRender; // Valid from OnStartGameFrame to BeginRenderViewFamily + FGameFramePtr LastFrameToRender; // Valid from OnStartGameFrame to BeginRenderViewFamily + uint32 NextLayerId; + TMap<uint32, FLayerPtr> LayerMap; + bool bNeedReAllocateViewportRenderTarget; + + // Render thread + FSettingsPtr Settings_RenderThread; + FGameFramePtr Frame_RenderThread; // Valid from BeginRenderViewFamily to PostRenderViewFamily_RenderThread + TArray<FLayerPtr> Layers_RenderThread; + FLayerPtr EyeLayer_RenderThread; // Valid to be accessed from game thread, since updated only when game thread is waiting + bool bNeedReAllocateDepthTexture_RenderThread; + bool bNeedReAllocateFoveationTexture_RenderThread; + bool bNeedReAllocateMotionVectorTexture_RenderThread; + + // RHI thread + FSettingsPtr Settings_RHIThread; + FGameFramePtr Frame_RHIThread; // Valid from PreRenderViewFamily_RenderThread to FinishRendering_RHIThread + TArray<FLayerPtr> Layers_RHIThread; + + + FHMDViewMesh HiddenAreaMeshes[2]; + FHMDViewMesh VisibleAreaMeshes[2]; + + FPerformanceStats PerformanceStats; + + FRotator SplashRotation; // rotation applied to all splash screens (dependent on HMD orientation as the splash is shown) + +#if !UE_BUILD_SHIPPING + FDelegateHandle DrawDebugDelegateHandle; +#endif + + enum class FInsightInitStatus + { + NotInitialized, + Initialized, + Failed, + }; + + FInsightInitStatus InsightInitStatus; + + bool bShutdownRequestQueued; + bool bEyeTrackedFoveatedRenderingSupported; + + TArray<FOculusXRHMDEventPollingDelegate> EventPollingDelegates; +}; + +typedef TSharedPtr< FOculusXRHMD, ESPMode::ThreadSafe > FOculusXRHMDPtr; + + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDModule.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c9a6e49a7c5cf25afb13fdf0eeba2ad8539624aa --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDModule.cpp @@ -0,0 +1,393 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMDModule.h" +#include "OculusXRHMD.h" +#include "OculusXRHMDPrivateRHI.h" +#include "OculusXRHMDRuntimeSettings.h" +#include "Containers/StringConv.h" +#include "Misc/EngineVersion.h" +#include "Misc/Paths.h" +#if PLATFORM_ANDROID + #include "Android/AndroidApplication.h" + #include "Android/AndroidPlatformMisc.h" +#endif +#include "Interfaces/IPluginManager.h" +#include "ShaderCore.h" + +//------------------------------------------------------------------------------------------------- +// FOculusXRHMDModule +//------------------------------------------------------------------------------------------------- + +OculusPluginWrapper FOculusXRHMDModule::PluginWrapper; + +FOculusXRHMDModule::FOculusXRHMDModule() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + bPreInit = false; + bPreInitCalled = false; + OVRPluginHandle = nullptr; + GraphicsAdapterLuid = 0; +#endif +} + +void FOculusXRHMDModule::StartupModule() +{ + IHeadMountedDisplayModule::StartupModule(); +} + +void FOculusXRHMDModule::ShutdownModule() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + if (PluginWrapper.IsInitialized()) + { + PluginWrapper.Shutdown2(); + OculusPluginWrapper::DestroyOculusPluginWrapper(&PluginWrapper); + } + + if (OVRPluginHandle) + { + FPlatformProcess::FreeDllHandle(OVRPluginHandle); + OVRPluginHandle = nullptr; + } +#endif +} + +#if PLATFORM_ANDROID +extern bool AndroidThunkCpp_IsOculusMobileApplication(); +#endif + +FString FOculusXRHMDModule::GetModuleKeyName() const +{ + return FString(TEXT("OculusXRHMD")); +} + +void FOculusXRHMDModule::GetModuleAliases(TArray<FString>& AliasesOut) const +{ + // Pre-OculusXR rename (5.0.3 v44) + AliasesOut.Add(TEXT("OculusHMD")); +} + +bool FOculusXRHMDModule::PreInit() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + if (!bPreInitCalled) + { + bPreInit = false; + + #if PLATFORM_ANDROID + bPreInitCalled = true; + if (!AndroidThunkCpp_IsOculusMobileApplication()) + { + UE_LOG(LogHMD, Log, TEXT("App is not packaged for Oculus Mobile")); + return false; + } + #endif + + // Init module if app can render + if (FApp::CanEverRender()) + { + // Load OVRPlugin + OVRPluginHandle = GetOVRPluginHandle(); + + if (!OVRPluginHandle) + { + UE_LOG(LogHMD, Log, TEXT("Failed loading OVRPlugin %s"), TEXT(OVRP_VERSION_STR)); + return false; + } + + if (!OculusPluginWrapper::InitializeOculusPluginWrapper(&PluginWrapper)) + { + UE_LOG(LogHMD, Log, TEXT("Failed InitializeOculusPluginWrapper")); + return false; + } + + // Initialize OVRPlugin + ovrpRenderAPIType PreinitApiType = ovrpRenderAPI_None; + #if PLATFORM_ANDROID + void* Activity = (void*)FAndroidApplication::GetGameActivityThis(); + PreinitApiType = ovrpRenderAPI_Vulkan; + #else + void* Activity = nullptr; + #endif + + if (OVRP_FAILURE(PluginWrapper.PreInitialize5(Activity, PreinitApiType, ovrpPreinitializeFlags::ovrpPreinitializeFlag_None))) + { + UE_LOG(LogHMD, Log, TEXT("Failed initializing OVRPlugin %s"), TEXT(OVRP_VERSION_STR)); +#if PLATFORM_WINDOWS + return true; +#else + return false; +#endif + } + + #if PLATFORM_WINDOWS + bPreInitCalled = true; + const LUID* DisplayAdapterId; + if (OVRP_SUCCESS(PluginWrapper.GetDisplayAdapterId2((const void**)&DisplayAdapterId)) && DisplayAdapterId) + { + SetGraphicsAdapterLuid(*(const uint64*)DisplayAdapterId); + } + else + { + UE_LOG(LogHMD, Log, TEXT("Could not determine HMD display adapter")); + } + + const WCHAR* AudioInDeviceId; + if (OVRP_SUCCESS(PluginWrapper.GetAudioInDeviceId2((const void**)&AudioInDeviceId)) && AudioInDeviceId) + { + GConfig->SetString(TEXT("Oculus.Settings"), TEXT("AudioInputDevice"), AudioInDeviceId, GEngineIni); + } + else + { + UE_LOG(LogHMD, Log, TEXT("Could not determine HMD audio input device")); + } + + const WCHAR* AudioOutDeviceId; + if (OVRP_SUCCESS(PluginWrapper.GetAudioOutDeviceId2((const void**)&AudioOutDeviceId)) && AudioOutDeviceId) + { + GConfig->SetString(TEXT("Oculus.Settings"), TEXT("AudioOutputDevice"), AudioOutDeviceId, GEngineIni); + } + else + { + UE_LOG(LogHMD, Log, TEXT("Could not determine HMD audio output device")); + } + #endif + + float ModulePriority; + if (!GConfig->GetFloat(TEXT("HMDPluginPriority"), *GetModuleKeyName(), ModulePriority, GEngineIni)) + { + // if user doesn't set priority set it for them to allow this hmd to be used if enabled + ModulePriority = 45.0f; + GConfig->SetFloat(TEXT("HMDPluginPriority"), *GetModuleKeyName(), ModulePriority, GEngineIni); + } + + UE_LOG(LogHMD, Log, TEXT("FOculusXRHMDModule PreInit successfully")); + + bPreInit = true; + } + } + + return bPreInit; +#else + return false; +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +} + +bool FOculusXRHMDModule::IsHMDConnected() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + UOculusXRHMDRuntimeSettings* HMDSettings = GetMutableDefault<UOculusXRHMDRuntimeSettings>(); + if (FApp::CanEverRender() && HMDSettings->XrApi != EOculusXRXrApi::NativeOpenXR) + { + return true; + } +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + return false; +} + +uint64 FOculusXRHMDModule::GetGraphicsAdapterLuid() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 || OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 + if (!GraphicsAdapterLuid) + { + int GraphicsAdapter; + + if (GConfig->GetInt(TEXT("Oculus.Settings"), TEXT("GraphicsAdapter"), GraphicsAdapter, GEngineIni) && GraphicsAdapter >= 0) + { + TRefCountPtr<IDXGIFactory> DXGIFactory; + TRefCountPtr<IDXGIAdapter> DXGIAdapter; + DXGI_ADAPTER_DESC DXGIAdapterDesc; + + if (SUCCEEDED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)DXGIFactory.GetInitReference())) && SUCCEEDED(DXGIFactory->EnumAdapters(GraphicsAdapter, DXGIAdapter.GetInitReference())) && SUCCEEDED(DXGIAdapter->GetDesc(&DXGIAdapterDesc))) + { + FMemory::Memcpy(&GraphicsAdapterLuid, &DXGIAdapterDesc.AdapterLuid, sizeof(GraphicsAdapterLuid)); + } + } + } +#endif + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + return GraphicsAdapterLuid; +#else + return 0; +#endif +} + +FString FOculusXRHMDModule::GetAudioInputDevice() +{ + FString AudioInputDevice; +#if OCULUS_HMD_SUPPORTED_PLATFORMS + GConfig->GetString(TEXT("Oculus.Settings"), TEXT("AudioInputDevice"), AudioInputDevice, GEngineIni); +#endif + return AudioInputDevice; +} + +FString FOculusXRHMDModule::GetAudioOutputDevice() +{ + FString AudioOutputDevice; +#if OCULUS_HMD_SUPPORTED_PLATFORMS + #if PLATFORM_WINDOWS + if (bPreInit) + { + if (FApp::CanEverRender()) + { + const WCHAR* audioOutDeviceId; + if (OVRP_SUCCESS(PluginWrapper.GetAudioOutDeviceId2((const void**)&audioOutDeviceId)) && audioOutDeviceId) + { + AudioOutputDevice = audioOutDeviceId; + } + } + } + #else + GConfig->GetString(TEXT("Oculus.Settings"), TEXT("AudioOutputDevice"), AudioOutputDevice, GEngineIni); + #endif +#endif + return AudioOutputDevice; +} + +TSharedPtr<class IXRTrackingSystem, ESPMode::ThreadSafe> FOculusXRHMDModule::CreateTrackingSystem() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + if (PreInit()) + { + OculusXRHMD::FOculusXRHMDPtr OculusXRHMD = FSceneViewExtensions::NewExtension<OculusXRHMD::FOculusXRHMD>(); + + if (OculusXRHMD->Startup()) + { + HeadMountedDisplay = OculusXRHMD; + return OculusXRHMD; + } + } + HeadMountedDisplay = nullptr; +#endif + return nullptr; +} + +TSharedPtr<IHeadMountedDisplayVulkanExtensions, ESPMode::ThreadSafe> FOculusXRHMDModule::GetVulkanExtensions() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + if (PreInit()) + { + if (!VulkanExtensions.IsValid()) + { + VulkanExtensions = MakeShareable(new OculusXRHMD::FVulkanExtensions); + } + } + return VulkanExtensions; +#endif + return nullptr; +} + +FString FOculusXRHMDModule::GetDeviceSystemName() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + ovrpSystemHeadset SystemHeadset; + if (PluginWrapper.IsInitialized() && OVRP_SUCCESS(PluginWrapper.GetSystemHeadsetType2(&SystemHeadset))) + { + switch (SystemHeadset) + { + case ovrpSystemHeadset_Oculus_Quest: + return FString("Oculus Quest"); + + case ovrpSystemHeadset_Oculus_Quest_2: + default: + return FString("Oculus Quest2"); + + #ifdef WITH_OCULUS_BRANCH + case ovrpSystemHeadset_Meta_Quest_Pro: + return FString("Meta Quest Pro"); + #endif // WITH_OCULUS_BRANCH + } + } + return FString(); +#else + return FString(); +#endif +} + +bool FOculusXRHMDModule::IsStandaloneStereoOnlyDevice() +{ +#if PLATFORM_ANDROID + return FAndroidMisc::GetDeviceMake() == FString("Oculus"); +#else + return false; +#endif +} + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +void* FOculusXRHMDModule::GetOVRPluginHandle() +{ + void* OVRPluginHandle = nullptr; + + #if PLATFORM_WINDOWS + FString XrApi; + if (!GConfig->GetString(TEXT("/Script/OculusXRHMD.OculusXRHMDRuntimeSettings"), TEXT("XrApi"), XrApi, GEngineIni) || XrApi.Equals(FString("OVRPluginOpenXR"))) + { + FString BinariesPath = FPaths::Combine(IPluginManager::Get().FindPlugin(TEXT("OculusXR"))->GetBaseDir(), TEXT("/Source/ThirdParty/OVRPlugin/OVRPlugin/Lib/Win64")); + FPlatformProcess::PushDllDirectory(*BinariesPath); + OVRPluginHandle = FPlatformProcess::GetDllHandle(*(BinariesPath / "OpenXR/OVRPlugin.dll")); + FPlatformProcess::PopDllDirectory(*BinariesPath); + } + #elif PLATFORM_ANDROID + OVRPluginHandle = FPlatformProcess::GetDllHandle(TEXT("libOVRPlugin.so")); + #endif // PLATFORM_ANDROID + + return OVRPluginHandle; +} + +bool FOculusXRHMDModule::PoseToOrientationAndPosition(const FQuat& InOrientation, const FVector& InPosition, FQuat& OutOrientation, FVector& OutPosition) const +{ + OculusXRHMD::CheckInGameThread(); + + OculusXRHMD::FOculusXRHMD* OculusXRHMD = static_cast<OculusXRHMD::FOculusXRHMD*>(HeadMountedDisplay.Pin().Get()); + + if (OculusXRHMD) + { + ovrpPosef InPose; + InPose.Orientation = OculusXRHMD::ToOvrpQuatf(InOrientation); + InPose.Position = OculusXRHMD::ToOvrpVector3f(InPosition); + OculusXRHMD::FPose OutPose; + + if (OculusXRHMD->ConvertPose(InPose, OutPose)) + { + OutOrientation = OutPose.Orientation; + OutPosition = OutPose.Position; + return true; + } + } + + return false; +} + +void FOculusXRHMDModule::SetGraphicsAdapterLuid(uint64 InLuid) +{ + GraphicsAdapterLuid = InLuid; + + #if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 || OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 + TRefCountPtr<IDXGIFactory> DXGIFactory; + + if (SUCCEEDED(CreateDXGIFactory(__uuidof(IDXGIFactory), (void**)DXGIFactory.GetInitReference()))) + { + for (int32 adapterIndex = 0;; adapterIndex++) + { + TRefCountPtr<IDXGIAdapter> DXGIAdapter; + DXGI_ADAPTER_DESC DXGIAdapterDesc; + + if (FAILED(DXGIFactory->EnumAdapters(adapterIndex, DXGIAdapter.GetInitReference())) || FAILED(DXGIAdapter->GetDesc(&DXGIAdapterDesc))) + { + break; + } + + if (!FMemory::Memcmp(&GraphicsAdapterLuid, &DXGIAdapterDesc.AdapterLuid, sizeof(GraphicsAdapterLuid))) + { + // Remember this adapterIndex so we use the right adapter, even when we startup without HMD connected + GConfig->SetInt(TEXT("Oculus.Settings"), TEXT("GraphicsAdapter"), adapterIndex, GEngineIni); + break; + } + } + } + #endif // OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 || OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 +} +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + +IMPLEMENT_MODULE(FOculusXRHMDModule, OculusXRHMD) diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDModule.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDModule.h new file mode 100644 index 0000000000000000000000000000000000000000..63c11dcc5e735a003b85831257770a630f3963fd --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDModule.h @@ -0,0 +1,111 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" +#include "IHeadMountedDisplay.h" +#include "OculusXRFunctionLibrary.h" +#include "OculusXRHMD_VulkanExtensions.h" +#include "OculusXRPluginWrapper.h" + +//------------------------------------------------------------------------------------------------- +// FOculusXRHMDModule +//------------------------------------------------------------------------------------------------- + +class FOculusXRHMDModule : public IOculusXRHMDModule +{ +public: + FOculusXRHMDModule(); + + static inline FOculusXRHMDModule& Get() + { + return FModuleManager::LoadModuleChecked< FOculusXRHMDModule >("OculusXRHMD"); + } + + // IModuleInterface + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + + // IHeadMountedDisplayModule + virtual FString GetModuleKeyName() const override; + virtual void GetModuleAliases(TArray<FString>& AliasesOut) const override; + virtual bool PreInit() override; + virtual bool IsHMDConnected() override; + virtual uint64 GetGraphicsAdapterLuid() override; + virtual FString GetAudioInputDevice() override; + virtual FString GetAudioOutputDevice() override; + virtual FString GetDeviceSystemName() override; + + virtual TSharedPtr< class IXRTrackingSystem, ESPMode::ThreadSafe > CreateTrackingSystem() override; + virtual TSharedPtr< IHeadMountedDisplayVulkanExtensions, ESPMode::ThreadSafe > GetVulkanExtensions() override; + virtual bool IsStandaloneStereoOnlyDevice() override; + + // IOculusXRHMDModule + virtual void GetPose(FRotator& DeviceRotation, FVector& DevicePosition, FVector& NeckPosition, bool bUseOrienationForPlayerCamera = false, bool bUsePositionForPlayerCamera = false, const FVector PositionScale = FVector::ZeroVector) override + { + UOculusXRFunctionLibrary::GetPose(DeviceRotation, DevicePosition, NeckPosition, bUseOrienationForPlayerCamera, bUsePositionForPlayerCamera, PositionScale); + } + + virtual void GetRawSensorData(FVector& AngularAcceleration, FVector& LinearAcceleration, FVector& AngularVelocity, FVector& LinearVelocity, float& TimeInSeconds) override + { + UOculusXRFunctionLibrary::GetRawSensorData(AngularAcceleration, LinearAcceleration, AngularVelocity, LinearVelocity, TimeInSeconds, EOculusXRTrackedDeviceType::HMD); + } + + virtual bool GetUserProfile(struct FOculusXRHmdUserProfile& Profile) override + { + return UOculusXRFunctionLibrary::GetUserProfile(Profile); + } + + virtual void SetBaseRotationAndBaseOffsetInMeters(FRotator Rotation, FVector BaseOffsetInMeters, EOrientPositionSelector::Type Options) override + { + UOculusXRFunctionLibrary::SetBaseRotationAndBaseOffsetInMeters(Rotation, BaseOffsetInMeters, Options); + } + + virtual void GetBaseRotationAndBaseOffsetInMeters(FRotator& OutRotation, FVector& OutBaseOffsetInMeters) override + { + UOculusXRFunctionLibrary::GetBaseRotationAndBaseOffsetInMeters(OutRotation, OutBaseOffsetInMeters); + } + + virtual void SetBaseRotationAndPositionOffset(FRotator BaseRot, FVector PosOffset, EOrientPositionSelector::Type Options) override + { + UOculusXRFunctionLibrary::SetBaseRotationAndPositionOffset(BaseRot, PosOffset, Options); + } + + virtual void GetBaseRotationAndPositionOffset(FRotator& OutRot, FVector& OutPosOffset) override + { + UOculusXRFunctionLibrary::GetBaseRotationAndPositionOffset(OutRot, OutPosOffset); + } + + virtual class IStereoLayers* GetStereoLayers() override + { + return UOculusXRFunctionLibrary::GetStereoLayers(); + } + + bool IsOVRPluginAvailable() const + { +#if OCULUS_HMD_SUPPORTED_PLATFORMS + return OVRPluginHandle != nullptr; +#else + return false; +#endif + } + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + OCULUSXRHMD_API static void* GetOVRPluginHandle(); + OCULUSXRHMD_API static inline OculusPluginWrapper& GetPluginWrapper() { return PluginWrapper; } + virtual bool PoseToOrientationAndPosition(const FQuat& InOrientation, const FVector& InPosition, FQuat& OutOrientation, FVector& OutPosition) const override; + +protected: + void SetGraphicsAdapterLuid(uint64 InLuid); + + static OculusPluginWrapper PluginWrapper; + + bool bPreInit; + bool bPreInitCalled; + void *OVRPluginHandle; + uint64 GraphicsAdapterLuid; + TWeakPtr< IHeadMountedDisplay, ESPMode::ThreadSafe > HeadMountedDisplay; + TSharedPtr< IHeadMountedDisplayVulkanExtensions, ESPMode::ThreadSafe > VulkanExtensions; + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivate.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivate.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c9eb9f32d9c188f8348d133698170ed6663dbc93 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivate.cpp @@ -0,0 +1,94 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMDPrivate.h" +#include "RHICommandList.h" +#include "RenderingThread.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// Utility functions +//------------------------------------------------------------------------------------------------- + +bool InGameThread() +{ + if (GIsGameThreadIdInitialized) + { + return FPlatformTLS::GetCurrentThreadId() == GGameThreadId; + } + else + { + return true; + } +} + + +bool InRenderThread() +{ + if (GIsThreadedRendering && !GIsRenderingThreadSuspended.Load(EMemoryOrder::Relaxed)) + { + return IsInActualRenderingThread(); + } + else + { + return InGameThread(); + } +} + + +bool InRHIThread() +{ + if (GIsThreadedRendering && !GIsRenderingThreadSuspended.Load(EMemoryOrder::Relaxed)) + { + if (IsRHIThreadRunning()) + { + if (IsInRHIThread()) + { + return true; + } + + if (IsInActualRenderingThread()) + { + return GetImmediateCommandList_ForRenderCommand().Bypass(); + } + + return false; + } + else + { + return IsInActualRenderingThread(); + } + } + else + { + return InGameThread(); + } +} + +bool ConvertPose_Internal(const FPose& InPose, FPose& OutPose, const FQuat BaseOrientation, const FVector BaseOffset, float WorldToMetersScale) +{ + // apply base orientation correction + OutPose.Orientation = BaseOrientation.Inverse() * InPose.Orientation; + OutPose.Orientation.Normalize(); + + // correct position according to BaseOrientation and BaseOffset. + OutPose.Position = (InPose.Position - BaseOffset) * WorldToMetersScale; + OutPose.Position = BaseOrientation.Inverse().RotateVector(OutPose.Position); + + return true; +} + +bool ConvertPose_Internal(const ovrpPosef& InPose, FPose& OutPose, const FQuat BaseOrientation, const FVector BaseOffset, float WorldToMetersScale) +{ + return ConvertPose_Internal(FPose(ToFQuat(InPose.Orientation), ToFVector(InPose.Position)), OutPose, BaseOrientation, BaseOffset, WorldToMetersScale); +} + +bool ConvertPose_Internal(const FPose& InPose, ovrpPosef& OutPose, const FQuat BaseOrientation, const FVector BaseOffset, float WorldToMetersScale) +{ + OutPose.Orientation = ToOvrpQuatf(BaseOrientation * InPose.Orientation); + OutPose.Position = ToOvrpVector3f(BaseOrientation.RotateVector(InPose.Position) / WorldToMetersScale + BaseOffset); + return true; +} + +} // namespace OculusXRHMD diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivate.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivate.h new file mode 100644 index 0000000000000000000000000000000000000000..d329fc7d65eb325d7825c227abf351510cfa7d6b --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivate.h @@ -0,0 +1,308 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "GameFramework/WorldSettings.h" +#include "IOculusXRHMDModule.h" +#include "OculusXRFunctionLibrary.h" +#include "OculusXRPassthroughLayerShapes.h" +#include "StereoRendering.h" +#include "HAL/RunnableThread.h" +#include "RHI.h" +#include <functional> + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#define OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 PLATFORM_WINDOWS +#define OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 PLATFORM_WINDOWS +#define OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN (PLATFORM_WINDOWS || PLATFORM_ANDROID) +#else +#define OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 0 +#define OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 0 +#define OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN 0 +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + + +//------------------------------------------------------------------------------------------------- +// OVRPlugin +//------------------------------------------------------------------------------------------------- + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRPluginWrapper.h" +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + +//------------------------------------------------------------------------------------------------- +// Utility functions +//------------------------------------------------------------------------------------------------- + +namespace OculusXRHMD +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + struct FPose + { + FQuat Orientation; + FVector Position; + + FPose() + : Orientation(EForceInit::ForceInit) + , Position(EForceInit::ForceInit) + {} + + FPose(const FQuat& InOrientation, const FVector& InPosition) : Orientation(InOrientation), Position(InPosition) {} + + FPose Inverse() const + { + FQuat InvOrientation = Orientation.Inverse(); + FVector InvPosition = InvOrientation.RotateVector(-Position); + return FPose(InvOrientation, InvPosition); + } + + FPose operator*(const FPose& other) const + { + return FPose(Orientation * other.Orientation, Orientation.RotateVector(other.Position) + Position); + } + }; + + /** Converts ovrpQuatf to FQuat */ + FORCEINLINE FQuat ToFQuat(const ovrpQuatf& InQuat) + { + return FQuat(-InQuat.z, InQuat.x, InQuat.y, -InQuat.w); + } + + /** Converts FQuat to ovrpQuatf */ + FORCEINLINE ovrpQuatf ToOvrpQuatf(const FQuat& InQuat) + { + return ovrpQuatf { static_cast<float>(InQuat.Y), static_cast<float>(InQuat.Z), static_cast<float>(-InQuat.X), static_cast<float>(-InQuat.W) }; + } + + /** Converts vector from Oculus to Unreal */ + FORCEINLINE FVector ToFVector(const ovrpVector3f& InVec) + { + return FVector(-InVec.z, InVec.x, InVec.y); + } + + /** Converts vector from Unreal to Oculus. */ + FORCEINLINE ovrpVector3f ToOvrpVector3f(const FVector& InVec) + { + return ovrpVector3f { static_cast<float>(InVec.Y), static_cast<float>(InVec.Z), static_cast<float>(-InVec.X) }; + } + + FORCEINLINE FMatrix ToFMatrix(const ovrpMatrix4f& vtm) + { + // Rows and columns are swapped between ovrpMatrix4f and FMatrix + return FMatrix( + FPlane(vtm.M[0][0], vtm.M[1][0], vtm.M[2][0], vtm.M[3][0]), + FPlane(vtm.M[0][1], vtm.M[1][1], vtm.M[2][1], vtm.M[3][1]), + FPlane(vtm.M[0][2], vtm.M[1][2], vtm.M[2][2], vtm.M[3][2]), + FPlane(vtm.M[0][3], vtm.M[1][3], vtm.M[2][3], vtm.M[3][3])); + } + + FORCEINLINE ovrpVector4f LinearColorToOvrpVector4f(const FLinearColor& InColor) + { + return ovrpVector4f{ InColor.R, InColor.G, InColor.B, InColor.A }; + } + + FORCEINLINE ovrpRecti ToOvrpRecti(const FIntRect& rect) + { + return ovrpRecti { { rect.Min.X, rect.Min.Y }, { rect.Size().X, rect.Size().Y } }; + } + + FORCEINLINE ovrpColorf ToOvrpColorf(const FLinearColor LinearColor) + { + return ovrpColorf { LinearColor.R, LinearColor.G , LinearColor.B, LinearColor.A }; + } + + FORCEINLINE ovrpMatrix4f ToOvrpMatrix(FMatrix Matrix) + { + ovrpMatrix4f Result; + + Result.M[0][0] = Matrix.M[0][0]; + Result.M[0][1] = Matrix.M[0][1]; + Result.M[0][2] = Matrix.M[0][2]; + Result.M[0][3] = Matrix.M[0][3]; + + Result.M[1][0] = Matrix.M[1][0]; + Result.M[1][1] = Matrix.M[1][1]; + Result.M[1][2] = Matrix.M[1][2]; + Result.M[1][3] = Matrix.M[1][3]; + + Result.M[2][0] = Matrix.M[2][0]; + Result.M[2][1] = Matrix.M[2][1]; + Result.M[2][2] = Matrix.M[2][2]; + Result.M[2][3] = Matrix.M[2][3]; + + Result.M[3][0] = Matrix.M[3][0]; + Result.M[3][1] = Matrix.M[3][1]; + Result.M[3][2] = Matrix.M[3][2]; + Result.M[3][3] = Matrix.M[3][3]; + + return Result; + } + + /** Helper that converts ovrTrackedDeviceType to EOculusXRTrackedDeviceType */ + FORCEINLINE EOculusXRTrackedDeviceType ToEOculusXRTrackedDeviceType(ovrpNode Source) + { + EOculusXRTrackedDeviceType Destination = EOculusXRTrackedDeviceType::All; // Best attempt at initialization + + switch (Source) + { + case ovrpNode_None: + Destination = EOculusXRTrackedDeviceType::None; + break; + case ovrpNode_Head: + Destination = EOculusXRTrackedDeviceType::HMD; + break; + case ovrpNode_HandLeft: + Destination = EOculusXRTrackedDeviceType::LTouch; + break; + case ovrpNode_HandRight: + Destination = EOculusXRTrackedDeviceType::RTouch; + break; + case ovrpNode_DeviceObjectZero: + Destination = EOculusXRTrackedDeviceType::DeviceObjectZero; + break; + default: + break; + } + return Destination; + } + + /** Helper that converts EOculusXRTrackedDeviceType to ovrTrackedDeviceType */ + FORCEINLINE ovrpNode ToOvrpNode(EOculusXRTrackedDeviceType Source) + { + ovrpNode Destination = ovrpNode_None; // Best attempt at initialization + + switch (Source) + { + case EOculusXRTrackedDeviceType::None: + Destination = ovrpNode_None; + break; + case EOculusXRTrackedDeviceType::HMD: + Destination = ovrpNode_Head; + break; + case EOculusXRTrackedDeviceType::LTouch: + Destination = ovrpNode_HandLeft; + break; + case EOculusXRTrackedDeviceType::RTouch: + Destination = ovrpNode_HandRight; + break; + case EOculusXRTrackedDeviceType::DeviceObjectZero: + Destination = ovrpNode_DeviceObjectZero; + break; + default: + break; + } + return Destination; + } + + FORCEINLINE int32 ToExternalDeviceId(const ovrpNode Source) + { + int32 ExternalDeviceId = INDEX_NONE; + switch (Source) + { + case ovrpNode_Head: + // required to be zero (see IXRTrackingSystem::HMDDeviceId) + ExternalDeviceId = 0; + break; + case ovrpNode_None: + case ovrpNode_Count: + case ovrpNode_EnumSize: + // ExternalDeviceId = INDEX_NONE; + break; + default: + // add one, in case the enum value is zero (conflicting with the HMD) + ExternalDeviceId = 1 + (int32)Source; + break; + } + return ExternalDeviceId; + } + + FORCEINLINE ovrpNode ToOvrpNode(const int32 ExternalDeviceId) + { + ovrpNode Destination = ovrpNode_None; + switch (ExternalDeviceId) + { + case 0: + // zero implies HMD (see ToExternalDeviceId/IXRTrackingSystem::HMDDeviceId) + Destination = ovrpNode_Head; + break; + case -1: + // Destination = ovrpNode_None; + break; + default: + // we added one to avoid collision with the HMD's ID (see ToExternalDeviceId) + Destination = ovrpNode(ExternalDeviceId - 1); + break; + } + return Destination; + } + + bool ConvertPose_Internal(const FPose& InPose, FPose& OutPose, const FQuat BaseOrientation, const FVector BaseOffset, float WorldToMetersScale); + + bool ConvertPose_Internal(const ovrpPosef& InPose, FPose& OutPose, const FQuat BaseOrientation, const FVector BaseOffset, float WorldToMetersScale); + + bool ConvertPose_Internal(const FPose& InPose, ovrpPosef& OutPose, const FQuat BaseOrientation, const FVector BaseOffset, float WorldToMetersScale); + + FORCEINLINE ovrpInsightPassthroughColorMapType ToOVRPColorMapType(EOculusXRColorMapType InColorMapType) + { + switch (InColorMapType) { + case ColorMapType_GrayscaleToColor: + return ovrpInsightPassthroughColorMapType_MonoToRgba; + case ColorMapType_Grayscale: + return ovrpInsightPassthroughColorMapType_MonoToMono; + case ColorMapType_ColorAdjustment: + return ovrpInsightPassthroughColorMapType_BrightnessContrastSaturation; + default: + return ovrpInsightPassthroughColorMapType_None; + } + } + + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS + + + /** Check currently executing from Game thread */ + OCULUSXRHMD_API bool InGameThread(); + + FORCEINLINE void CheckInGameThread() + { +#if DO_CHECK + check(InGameThread()); +#endif + } + + + /** Check currently executing from Render thread */ + OCULUSXRHMD_API bool InRenderThread(); + + FORCEINLINE void CheckInRenderThread() + { +#if DO_CHECK + check(InRenderThread()); +#endif + } + + + /** Check currently executing from RHI thread */ + OCULUSXRHMD_API bool InRHIThread(); + + FORCEINLINE void CheckInRHIThread() + { +#if DO_CHECK + check(InRHIThread()); +#endif + } + + FORCEINLINE bool GetUnitScaleFactorFromSettings(UWorld* World, float& outWorldToMeters) + { + if (IsValid(World)) + { + const auto* WorldSettings = World->GetWorldSettings(); + if (IsValid(WorldSettings)) + { + outWorldToMeters = WorldSettings->WorldToMeters; + return true; + } + } + return false; + } + +} // namespace OculusXRHMD diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivateRHI.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivateRHI.h new file mode 100644 index 0000000000000000000000000000000000000000..d52213c2717df4496442acafb571e37eba950091 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDPrivateRHI.h @@ -0,0 +1,64 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + +//------------------------------------------------------------------------------------------------- +// D3D11 +//------------------------------------------------------------------------------------------------- + +#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 +#include "ID3D11DynamicRHI.h" +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 + + +//------------------------------------------------------------------------------------------------- +// D3D12 +//------------------------------------------------------------------------------------------------- + +#if OCULUS_HMD_SUPPORTED_PLATFORMS_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 "ID3D12DynamicRHI.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 +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 + +//------------------------------------------------------------------------------------------------- +// Vulkan +//------------------------------------------------------------------------------------------------- + +#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN +#include "IVulkanDynamicRHI.h" +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDRuntimeSettings.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDRuntimeSettings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4cd21b0570dd72446ae3ed7570ccccbbee5beba6 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMDRuntimeSettings.cpp @@ -0,0 +1,220 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMDRuntimeSettings.h" + +////////////////////////////////////////////////////////////////////////// +// UOculusXRHMDRuntimeSettings + +#include "OculusXRHMD_Settings.h" + +UOculusXRHMDRuntimeSettings::UOculusXRHMDRuntimeSettings(const FObjectInitializer& ObjectInitializer) +: Super(ObjectInitializer) +, bAutoEnabled(false) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + // FSettings is the sole source of truth for Oculus default settings + OculusXRHMD::FSettings DefaultSettings; + bSupportsDash = DefaultSettings.Flags.bSupportsDash; + bCompositesDepth = DefaultSettings.Flags.bCompositeDepth; + bHQDistortion = DefaultSettings.Flags.bHQDistortion; + SuggestedCpuPerfLevel = DefaultSettings.SuggestedCpuPerfLevel; + SuggestedGpuPerfLevel = DefaultSettings.SuggestedGpuPerfLevel; + FoveatedRenderingMethod = DefaultSettings.FoveatedRenderingMethod; + FoveatedRenderingLevel = DefaultSettings.FoveatedRenderingLevel; + bDynamicFoveatedRendering = DefaultSettings.bDynamicFoveatedRendering; + bSupportEyeTrackedFoveatedRendering = DefaultSettings.bSupportEyeTrackedFoveatedRendering; + PixelDensityMin = DefaultSettings.PixelDensityMin; + PixelDensityMax = DefaultSettings.PixelDensityMax; + bFocusAware = DefaultSettings.Flags.bFocusAware; + XrApi = DefaultSettings.XrApi; + ColorSpace = DefaultSettings.ColorSpace; + ControllerPoseAlignment = DefaultSettings.ControllerPoseAlignment; + bRequiresSystemKeyboard = DefaultSettings.Flags.bRequiresSystemKeyboard; + HandTrackingSupport = DefaultSettings.HandTrackingSupport; + HandTrackingFrequency = DefaultSettings.HandTrackingFrequency; + bInsightPassthroughEnabled = DefaultSettings.Flags.bInsightPassthroughEnabled; + bBodyTrackingEnabled = DefaultSettings.Flags.bBodyTrackingEnabled; + bEyeTrackingEnabled = DefaultSettings.Flags.bEyeTrackingEnabled; + bFaceTrackingEnabled = DefaultSettings.Flags.bFaceTrackingEnabled; + bSupportExperimentalFeatures = DefaultSettings.bSupportExperimentalFeatures; + bAnchorSupportEnabled = DefaultSettings.Flags.bAnchorSupportEnabled; + +#else + // Some set of reasonable defaults, since blueprints are still available on non-Oculus platforms. + bSupportsDash = false; + bCompositesDepth = false; + bHQDistortion = false; + SuggestedCpuPerfLevel = EOculusXRProcessorPerformanceLevel::SustainedLow; + SuggestedGpuPerfLevel = EOculusXRProcessorPerformanceLevel::SustainedHigh; + FoveatedRenderingMethod = EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering; + FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::Off; + bDynamicFoveatedRendering = false; + bSupportEyeTrackedFoveatedRendering = false; + PixelDensityMin = 0.5f; + PixelDensityMax = 1.0f; + bFocusAware = true; + XrApi = EOculusXRXrApi::OVRPluginOpenXR; + bLateLatching = false; + ColorSpace = EOculusXRColorSpace::P3; + ControllerPoseAlignment = EOculusXRControllerPoseAlignment::Default; + bRequiresSystemKeyboard = false; + HandTrackingSupport = EOculusXRHandTrackingSupport::ControllersOnly; + HandTrackingFrequency = EOculusXRHandTrackingFrequency::Low; + bInsightPassthroughEnabled = false; + bSupportExperimentalFeatures = false; + bBodyTrackingEnabled = false; + bEyeTrackingEnabled = false; + bFaceTrackingEnabled = false; + bAnchorSupportEnabled = false; +#endif + + LoadFromIni(); + RenameProperties(); +} + +#if WITH_EDITOR +bool UOculusXRHMDRuntimeSettings::CanEditChange(const FProperty* InProperty) const +{ + bool bIsEditable = Super::CanEditChange(InProperty); + + if (bIsEditable && InProperty) + { + const FName PropertyName = InProperty->GetFName(); + +// Disable settings for marketplace release that are only compatible with the Oculus engine fork +#ifndef WITH_OCULUS_BRANCH + if (PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingMethod) || + PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingLevel) || + PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bDynamicFoveatedRendering) || + PropertyName == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bSupportEyeTrackedFoveatedRendering)) + { + bIsEditable = false; + } +#endif // WITH_OCULUS_BRANCH + } + + return bIsEditable; +} + +void UOculusXRHMDRuntimeSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) +{ + Super::PostEditChangeProperty(PropertyChangedEvent); + + if (PropertyChangedEvent.Property != nullptr) + { + // Automatically switch to Fixed Foveated Rendering when removing Eye Tracked Foveated rendering support + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bSupportEyeTrackedFoveatedRendering) && + !bSupportEyeTrackedFoveatedRendering) + { + FoveatedRenderingMethod = EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering; + UpdateSinglePropertyInConfigFile(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingMethod)), GetDefaultConfigFilename()); + } + // Automatically enable support for eye tracked foveated rendering when selecting the Eye Tracked Foveated Rendering method + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingMethod) && + FoveatedRenderingMethod == EOculusXRFoveatedRenderingMethod::EyeTrackedFoveatedRendering) + { + bSupportEyeTrackedFoveatedRendering = true; + UpdateSinglePropertyInConfigFile(GetClass()->FindPropertyByName(GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, bSupportEyeTrackedFoveatedRendering)), GetDefaultConfigFilename()); + } + + if (PropertyChangedEvent.Property->GetFName() == GET_MEMBER_NAME_CHECKED(UOculusXRHMDRuntimeSettings, SupportedDevices)) + { + if (PropertyChangedEvent.ChangeType == EPropertyChangeType::ArrayAdd) + { + // Get a list of all available devices + TArray<EOculusXRSupportedDevices> deviceList; +#define OCULUS_DEVICE_LOOP(device) deviceList.Add(device); + FOREACH_ENUM_EOCULUSXRSUPPORTEDDEVICES(OCULUS_DEVICE_LOOP); +#undef OCULUS_DEVICE_LOOP + // Add last device that isn't already in the list + for (int i = deviceList.Num() - 1; i >= 0; --i) + { + if (!SupportedDevices.Contains(deviceList[i])) + { + SupportedDevices.Last() = deviceList[i]; + break; + } + // Just add another copy of the first device if nothing was available + SupportedDevices.Last() = deviceList[deviceList.Num() - 1]; + } + } + } + } +} +#endif // WITH_EDITOR + +void UOculusXRHMDRuntimeSettings::LoadFromIni() +{ + const TCHAR* OculusSettings = TEXT("Oculus.Settings"); + bool v; + float f; + FVector vec; + + if (GConfig->GetFloat(OculusSettings, TEXT("PixelDensityMax"), f, GEngineIni)) + { + check(!FMath::IsNaN(f)); + PixelDensityMax = f; + } + if (GConfig->GetFloat(OculusSettings, TEXT("PixelDensityMin"), f, GEngineIni)) + { + check(!FMath::IsNaN(f)); + PixelDensityMin = f; + } + if (GConfig->GetBool(OculusSettings, TEXT("bHQDistortion"), v, GEngineIni)) + { + bHQDistortion = v; + } + if (GConfig->GetBool(OculusSettings, TEXT("bCompositeDepth"), v, GEngineIni)) + { + bCompositesDepth = v; + } +} + +/** This essentially acts like redirects for plugin settings saved in the engine config. + Anything added here should check for the current setting in the config so that if the dev changes the setting manually, we don't overwrite it with the old setting. + Note: Do not use UpdateSinglePropertyInConfigFile() here, since that uses a temp config to save the single property, + it'll get overwritten when GConfig->RemoveKey() marks the main config as dirty and it gets saved again **/ +void UOculusXRHMDRuntimeSettings::RenameProperties() +{ + const TCHAR* OculusSettings = TEXT("/Script/OculusXRHMD.OculusXRHMDRuntimeSettings"); + bool v = false; + FString str; + + // FFRLevel was renamed to FoveatedRenderingLevel + if (!GConfig->GetString(OculusSettings, GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingLevel), str, GetDefaultConfigFilename()) && + GConfig->GetString(OculusSettings, TEXT("FFRLevel"), str, GetDefaultConfigFilename())) + { + if (str.Equals(TEXT("FFR_Off"))) + { + FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::Off; + } + else if (str.Equals(TEXT("FFR_Low"))) + { + FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::Low; + } + else if (str.Equals(TEXT("FFR_Medium"))) + { + FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::Medium; + } + else if (str.Equals(TEXT("FFR_High"))) + { + FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::High; + } + else if (str.Equals(TEXT("FFR_HighTop"))) + { + FoveatedRenderingLevel = EOculusXRFoveatedRenderingLevel::HighTop; + } + // Use UEnum::GetDisplayValueAsText().ToString() here because UEnum::GetValueAsString() includes the type name as well + GConfig->SetString(OculusSettings, GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, FoveatedRenderingLevel), *UEnum::GetDisplayValueAsText(FoveatedRenderingLevel).ToString(), GetDefaultConfigFilename()); + GConfig->RemoveKey(OculusSettings, TEXT("FFRLevel"), GetDefaultConfigFilename()); + } + + // FFRDynamic was renamed to bDynamicFoveatedRendering + if (!GConfig->GetString(OculusSettings, GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, bDynamicFoveatedRendering), str, GetDefaultConfigFilename()) && + GConfig->GetBool(OculusSettings, TEXT("FFRDynamic"), v, GetDefaultConfigFilename())) + { + bDynamicFoveatedRendering = v; + GConfig->SetBool(OculusSettings, GET_MEMBER_NAME_STRING_CHECKED(UOculusXRHMDRuntimeSettings, bDynamicFoveatedRendering), bDynamicFoveatedRendering, GetDefaultConfigFilename()); + GConfig->RemoveKey(OculusSettings, TEXT("FFRDynamic"), GetDefaultConfigFilename()); + } +} diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_ConsoleCommands.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_ConsoleCommands.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8eda55a2c8f20e3ae57e099211c0f9fd64955436 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_ConsoleCommands.cpp @@ -0,0 +1,89 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "OculusXRHMD_ConsoleCommands.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD.h" +#include "OculusXRSceneCaptureCubemap.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FConsoleCommands +//------------------------------------------------------------------------------------------------- + +/// @cond DOXYGEN_WARNINGS + +FConsoleCommands::FConsoleCommands(class FOculusXRHMD* InHMDPtr) + : UpdateOnRenderThreadCommand(TEXT("vr.oculus.bUpdateOnRenderThread"), + *NSLOCTEXT("OculusRift", "CCommandText_UpdateRT", "Oculus Rift specific extension.\nEnables or disables updating on the render thread.").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::UpdateOnRenderThreadCommandHandler)) + , PixelDensityMinCommand(TEXT("vr.oculus.PixelDensity.min"), + *NSLOCTEXT("OculusRift", "CCommandText_PixelDensityMin", "Oculus Rift specific extension.\nMinimum pixel density when adaptive pixel density is enabled").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::PixelDensityMinCommandHandler)) + , PixelDensityMaxCommand(TEXT("vr.oculus.PixelDensity.max"), + *NSLOCTEXT("OculusRift", "CCommandText_PixelDensityMax", "Oculus Rift specific extension.\nMaximum pixel density when adaptive pixel density is enabled").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::PixelDensityMaxCommandHandler)) + , HQBufferCommand(TEXT("vr.oculus.bHQBuffer"), + *NSLOCTEXT("OculusRift", "CCommandText_HQBuffer", "Oculus Rift specific extension.\nEnable or disable using floating point texture format for the eye layer.").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::HQBufferCommandHandler)) + , HQDistortionCommand(TEXT("vr.oculus.bHQDistortion"), + *NSLOCTEXT("OculusRift", "CCommandText_HQDistortion", "Oculus Rift specific extension.\nEnable or disable using multiple mipmap levels for the eye layer.").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::HQDistortionCommandHandler)) + , ShowGlobalMenuCommand(TEXT("vr.oculus.ShowGlobalMenu"), + *NSLOCTEXT("OculusRift", "CCommandText_GlobalMenu", "Oculus Rift specific extension.\nOpens the global menu.").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::ShowGlobalMenuCommandHandler)) + , ShowQuitMenuCommand(TEXT("vr.oculus.ShowQuitMenu"), + *NSLOCTEXT("OculusRift", "CCommandText_QuitMenu", "Oculus Rift specific extension.\nOpens the quit menu.").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::ShowQuitMenuCommandHandler)) + +#if !UE_BUILD_SHIPPING + , StatsCommand(TEXT("vr.oculus.Debug.bShowStats"), + *NSLOCTEXT("OculusRift", "CCommandText_Stats", "Oculus Rift specific extension.\nEnable or disable rendering of stats.").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::StatsCommandHandler)) + , CubemapCommand(TEXT("vr.oculus.Debug.CaptureCubemap"), + *NSLOCTEXT("OculusRift", "CCommandText_Cubemap", "Oculus Rift specific extension.\nCaptures a cubemap for Oculus Home.\nOptional arguments (default is zero for all numeric arguments):\n xoff=<float> -- X axis offset from the origin\n yoff=<float> -- Y axis offset\n zoff=<float> -- Z axis offset\n yaw=<float> -- the direction to look into (roll and pitch is fixed to zero)\n mobile -- Generate a Mobile format cubemap\n (height of the captured cubemap will be 1024 instead of 2048 pixels)\n").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(&UOculusXRSceneCaptureCubemap::CaptureCubemapCommandHandler)) + , ShowSettingsCommand(TEXT("vr.oculus.Debug.Show"), + *NSLOCTEXT("OculusRift", "CCommandText_Show", "Oculus Rift specific extension.\nShows the current value of various stereo rendering params.").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::ShowSettingsCommandHandler)) + , IPDCommand(TEXT("vr.oculus.Debug.IPD"), + *NSLOCTEXT("OculusRift", "CCommandText_IPD", "Oculus Rift specific extension.\nShows or changes the current interpupillary distance in meters.").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateRaw(InHMDPtr, &FOculusXRHMD::IPDCommandHandler)) +#endif // !UE_BUILD_SHIPPING +{ +} + +bool FConsoleCommands::Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) +{ + const TCHAR* OrigCmd = Cmd; + FString AliasedCommand; + + if (FParse::Command(&Cmd, TEXT("OVRGLOBALMENU"))) + { + AliasedCommand = TEXT("vr.oculus.ShowGlobalMenu"); + } + else if (FParse::Command(&Cmd, TEXT("OVRQUITMENU"))) + { + AliasedCommand = TEXT("vr.oculus.ShowQuitMenu"); + } +#if !UE_BUILD_SHIPPING + else if (FParse::Command(&Cmd, TEXT("vr.oculus.Debug.EnforceHeadTracking"))) + { + AliasedCommand = TEXT("vr.HeadTracking.bEnforced"); + } +#endif // !UE_BUILD_SHIPPING + + if (!AliasedCommand.IsEmpty()) + { + Ar.Logf(ELogVerbosity::Warning, TEXT("%s is deprecated. Use %s instead"), OrigCmd, *AliasedCommand); + return IConsoleManager::Get().ProcessUserConsoleInput(*AliasedCommand, Ar, InWorld); + } + return false; +} + +/// @endcond + +} // namespace OculusHmd + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_ConsoleCommands.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_ConsoleCommands.h new file mode 100644 index 0000000000000000000000000000000000000000..9b65047df0a88091498159f07fce5cf3092f8f01 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_ConsoleCommands.h @@ -0,0 +1,44 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "HAL/IConsoleManager.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FConsoleCommands +//------------------------------------------------------------------------------------------------- + +class FConsoleCommands : private FSelfRegisteringExec +{ +public: + FConsoleCommands(class FOculusXRHMD* InHMDPtr); + + // FSelfRegisteringExec interface + virtual bool Exec(UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar) override; +private: + FAutoConsoleCommand UpdateOnRenderThreadCommand; + FAutoConsoleCommand PixelDensityMinCommand; + FAutoConsoleCommand PixelDensityMaxCommand; + FAutoConsoleCommand HQBufferCommand; + FAutoConsoleCommand HQDistortionCommand; + FAutoConsoleCommand ShowGlobalMenuCommand; + FAutoConsoleCommand ShowQuitMenuCommand; + +#if !UE_BUILD_SHIPPING + // Debug console commands + FAutoConsoleCommand StatsCommand; + FAutoConsoleCommand CubemapCommand; + FAutoConsoleCommand ShowSettingsCommand; + FAutoConsoleCommand IPDCommand; +#endif // !UE_BUILD_SHIPPING +}; + + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..12690820596ff360fc902a13b83d0367547717a4 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent.cpp @@ -0,0 +1,511 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_CustomPresent.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD.h" +#include "ScreenRendering.h" +#include "PipelineStateCache.h" +#include "ClearQuad.h" +#include "OculusShaders.h" +#include "CommonRenderResources.h" + +#if PLATFORM_ANDROID +#include "Android/AndroidJNI.h" +#include "Android/AndroidEGL.h" +#include "Android/AndroidApplication.h" +#include "Android/AndroidPlatformMisc.h" +#endif + +#define VULKAN_CUBEMAP_POSITIVE_Y 2 +#define VULKAN_CUBEMAP_NEGATIVE_Y 3 + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FCustomPresent +//------------------------------------------------------------------------------------------------- + +FCustomPresent::FCustomPresent(class FOculusXRHMD* InOculusXRHMD, ovrpRenderAPIType InRenderAPI, EPixelFormat InDefaultPixelFormat, bool bInSupportsSRGB) + : OculusXRHMD(InOculusXRHMD) + , RenderAPI(InRenderAPI) + , DefaultPixelFormat(InDefaultPixelFormat) + , bSupportsSRGB(bInSupportsSRGB) + , bSupportsSubsampled(false) + , bIsStandaloneStereoDevice(false) +{ + CheckInGameThread(); + + DefaultOvrpTextureFormat = GetOvrpTextureFormat(GetDefaultPixelFormat()); + DefaultDepthOvrpTextureFormat = ovrpTextureFormat_None; + +#if PLATFORM_ANDROID + bIsStandaloneStereoDevice = FAndroidMisc::GetDeviceMake() == FString("Oculus"); +#endif + + // grab a pointer to the renderer module for displaying our mirror window + static const FName RendererModuleName("Renderer"); + RendererModule = FModuleManager::GetModulePtr<IRendererModule>(RendererModuleName); +} + + +void FCustomPresent::ReleaseResources_RHIThread() +{ + CheckInRHIThread(); + + if (MirrorTextureRHI.IsValid()) + { + FOculusXRHMDModule::GetPluginWrapper().DestroyMirrorTexture2(); + MirrorTextureRHI = nullptr; + } +} + + +void FCustomPresent::Shutdown() +{ + CheckInGameThread(); + + // OculusXRHMD is going away, but this object can live on until viewport is destroyed + ExecuteOnRenderThread([this]() + { + ExecuteOnRHIThread([this]() + { + OculusXRHMD = nullptr; + }); + }); +} + + +bool FCustomPresent::NeedsNativePresent() +{ + return !bIsStandaloneStereoDevice; +} + + +bool FCustomPresent::Present(int32& SyncInterval) +{ + CheckInRHIThread(); + + if (OculusXRHMD) + { + FGameFrame* Frame_RHIThread = OculusXRHMD->GetFrame_RHIThread(); + if (Frame_RHIThread) + { + FinishRendering_RHIThread(); + } + } + + SyncInterval = 0; // VSync off + + return NeedsNativePresent(); +} + + +void FCustomPresent::UpdateMirrorTexture_RenderThread() +{ + SCOPE_CYCLE_COUNTER(STAT_BeginRendering); + + CheckInRenderThread(); + + const ESpectatorScreenMode MirrorWindowMode = OculusXRHMD->GetSpectatorScreenMode_RenderThread(); + const FVector2D MirrorWindowSize = OculusXRHMD->GetFrame_RenderThread()->WindowSize; + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + // Need to destroy mirror texture? + if (MirrorTextureRHI.IsValid() && (MirrorWindowMode != ESpectatorScreenMode::Distorted || + MirrorWindowSize != FVector2D(MirrorTextureRHI->GetSizeX(), MirrorTextureRHI->GetSizeY()))) + { + ExecuteOnRHIThread([]() + { + FOculusXRHMDModule::GetPluginWrapper().DestroyMirrorTexture2(); + }); + + MirrorTextureRHI = nullptr; + } + + // Need to create mirror texture? + if (!MirrorTextureRHI.IsValid() && + MirrorWindowMode == ESpectatorScreenMode::Distorted && + MirrorWindowSize.X != 0 && MirrorWindowSize.Y != 0) + { + int Width = (int)MirrorWindowSize.X; + int Height = (int)MirrorWindowSize.Y; + ovrpTextureHandle TextureHandle; + + ExecuteOnRHIThread([&]() + { + FOculusXRHMDModule::GetPluginWrapper().SetupMirrorTexture2(GetOvrpDevice(), Height, Width, GetDefaultOvrpTextureFormat(), &TextureHandle); + }); + + UE_LOG(LogHMD, Log, TEXT("Allocated a new mirror texture (size %d x %d)"), Width, Height); + + ETextureCreateFlags TexCreateFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable; + + MirrorTextureRHI = CreateTexture_RenderThread(Width, Height, GetDefaultPixelFormat(), FClearValueBinding::None, 1, 1, 1, RRT_Texture2D, TextureHandle, TexCreateFlags)->GetTexture2D(); + } + } +} + + +void FCustomPresent::FinishRendering_RHIThread() +{ + SCOPE_CYCLE_COUNTER(STAT_FinishRendering); + CheckInRHIThread(); + +#if STATS + if (OculusXRHMD->GetFrame_RHIThread()->ShowFlags.Rendering) + { + ovrpAppLatencyTimings AppLatencyTimings; + if(OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetAppLatencyTimings2(&AppLatencyTimings))) + { + SET_FLOAT_STAT(STAT_LatencyRender, AppLatencyTimings.LatencyRender * 1000.0f); + SET_FLOAT_STAT(STAT_LatencyTimewarp, AppLatencyTimings.LatencyTimewarp * 1000.0f); + SET_FLOAT_STAT(STAT_LatencyPostPresent, AppLatencyTimings.LatencyPostPresent * 1000.0f); + SET_FLOAT_STAT(STAT_ErrorRender, AppLatencyTimings.ErrorRender * 1000.0f); + SET_FLOAT_STAT(STAT_ErrorTimewarp, AppLatencyTimings.ErrorTimewarp * 1000.0f); + } + } +#endif + + OculusXRHMD->FinishRHIFrame_RHIThread(); + +#if PLATFORM_ANDROID + float GPUFrameTime = 0.0f; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetGPUFrameTime(&GPUFrameTime))) + { + SubmitGPUFrameTime(GPUFrameTime); + } +#endif +} + + +EPixelFormat FCustomPresent::GetPixelFormat(EPixelFormat Format) const +{ + switch (Format) + { +// case PF_B8G8R8A8: + case PF_FloatRGBA: + case PF_FloatR11G11B10: +// case PF_R8G8B8A8: + return Format; + } + + return GetDefaultPixelFormat(); +} + + +EPixelFormat FCustomPresent::GetPixelFormat(ovrpTextureFormat Format) const +{ + switch(Format) + { +// case ovrpTextureFormat_R8G8B8A8_sRGB: +// case ovrpTextureFormat_R8G8B8A8: +// return PF_R8G8B8A8; + case ovrpTextureFormat_R16G16B16A16_FP: + return PF_FloatRGBA; + case ovrpTextureFormat_R11G11B10_FP: + return PF_FloatR11G11B10; +// case ovrpTextureFormat_B8G8R8A8_sRGB: +// case ovrpTextureFormat_B8G8R8A8: +// return PF_B8G8R8A8; + } + + return GetDefaultPixelFormat(); +} + + +ovrpTextureFormat FCustomPresent::GetOvrpTextureFormat(EPixelFormat Format, bool usesRGB) const +{ + switch (GetPixelFormat(Format)) + { + case PF_B8G8R8A8: + return bSupportsSRGB && usesRGB ? ovrpTextureFormat_B8G8R8A8_sRGB : ovrpTextureFormat_B8G8R8A8; + case PF_FloatRGBA: + return ovrpTextureFormat_R16G16B16A16_FP; + case PF_FloatR11G11B10: + return ovrpTextureFormat_R11G11B10_FP; + case PF_R8G8B8A8: + return bSupportsSRGB && usesRGB ? ovrpTextureFormat_R8G8B8A8_sRGB : ovrpTextureFormat_R8G8B8A8; + } + + return ovrpTextureFormat_None; +} + + +bool FCustomPresent::IsSRGB(ovrpTextureFormat InFormat) +{ + switch (InFormat) + { + case ovrpTextureFormat_B8G8R8A8_sRGB: + case ovrpTextureFormat_R8G8B8A8_sRGB: + return true; + } + + return false; +} + + +int FCustomPresent::GetSystemRecommendedMSAALevel() const +{ + int SystemRecommendedMSAALevel = 1; + FOculusXRHMDModule::GetPluginWrapper().GetSystemRecommendedMSAALevel2(&SystemRecommendedMSAALevel); + return SystemRecommendedMSAALevel; +} + + +FXRSwapChainPtr FCustomPresent::CreateSwapChain_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, const TArray<ovrpTextureHandle>& InTextures, ETextureCreateFlags InTexCreateFlags, const TCHAR* DebugName) +{ + CheckInRenderThread(); + + FTextureRHIRef RHITexture; + TArray<FTextureRHIRef> RHITextureSwapChain; + { + for (int32 TextureIndex = 0; TextureIndex < InTextures.Num(); ++TextureIndex) + { + FTextureRHIRef TexRef = CreateTexture_RenderThread(InSizeX, InSizeY, InFormat, InBinding, InNumMips, InNumSamples, InNumSamplesTileMem, InResourceType, InTextures[TextureIndex], InTexCreateFlags); + + FString TexName = FString::Printf(TEXT("%s (%d/%d)"), DebugName, TextureIndex, InTextures.Num()); + TexRef->SetName(*TexName); + RHIBindDebugLabelName(TexRef, *TexName); + + RHITextureSwapChain.Add(TexRef); + } + } + + RHITexture = GDynamicRHI->RHICreateAliasedTexture(RHITextureSwapChain[0]); + + return CreateXRSwapChain(MoveTemp(RHITextureSwapChain), RHITexture); +} + + +void FCustomPresent::CopyTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture* DstTexture, FRHITexture* SrcTexture, + FIntRect DstRect, FIntRect SrcRect, bool bAlphaPremultiply, bool bNoAlphaWrite, bool bInvertY, bool sRGBSource, bool bInvertAlpha) const +{ + CheckInRenderThread(); + + FRHITexture2D* DstTexture2D = DstTexture->GetTexture2D(); + FRHITextureCube* DstTextureCube = DstTexture->GetTextureCube(); + FRHITexture2D* SrcTexture2D = SrcTexture->GetTexture2DArray() ? SrcTexture->GetTexture2DArray() : SrcTexture->GetTexture2D(); + FRHITextureCube* SrcTextureCube = SrcTexture->GetTextureCube(); + + FIntPoint DstSize; + FIntPoint SrcSize; + + if (DstTexture2D && SrcTexture2D) + { + DstSize = FIntPoint(DstTexture2D->GetSizeX(), DstTexture2D->GetSizeY()); + SrcSize = FIntPoint(SrcTexture2D->GetSizeX(), SrcTexture2D->GetSizeY()); + } + else if(DstTextureCube && SrcTextureCube) + { + DstSize = FIntPoint(DstTextureCube->GetSize(), DstTextureCube->GetSize()); + SrcSize = FIntPoint(SrcTextureCube->GetSize(), SrcTextureCube->GetSize()); + } + else + { + return; + } + + if (DstRect.IsEmpty()) + { + DstRect = FIntRect(FIntPoint::ZeroValue, DstSize); + } + + if (SrcRect.IsEmpty()) + { + SrcRect = FIntRect(FIntPoint::ZeroValue, SrcSize); + } + + const uint32 ViewportWidth = DstRect.Width(); + const uint32 ViewportHeight = DstRect.Height(); + const FIntPoint TargetSize(ViewportWidth, ViewportHeight); + float U = SrcRect.Min.X / (float) SrcSize.X; + float V = SrcRect.Min.Y / (float) SrcSize.Y; + float USize = SrcRect.Width() / (float) SrcSize.X; + float VSize = SrcRect.Height() / (float) SrcSize.Y; + +#if PLATFORM_ANDROID // on android, top-left isn't 0/0 but 1/0. + if (bInvertY) + { + V = 1.0f - V; + VSize = -VSize; + } +#endif + + FRHITexture* SrcTextureRHI = SrcTexture; + RHICmdList.Transition(FRHITransitionInfo(SrcTextureRHI, ERHIAccess::Unknown, ERHIAccess::SRVGraphics)); + FGraphicsPipelineStateInitializer GraphicsPSOInit; + + if(bInvertAlpha) + { + // write RGBA, RGB = src.rgb * 1 + dst.rgb * 0, A = src.a * 0 + dst.a * (1 - src.a) + GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_One, BF_Zero, BO_Add, BF_Zero, BF_InverseSourceAlpha >::GetRHI(); + } + else if (bAlphaPremultiply) + { + if (bNoAlphaWrite) + { + // for quads, write RGB, RGB = src.rgb * 1 + dst.rgb * 0 + GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB, BO_Add, BF_One, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI(); + } + else + { + // for quads, write RGBA, RGB = src.rgb * src.a + dst.rgb * 0, A = src.a + dst.a * 0 + GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_SourceAlpha, BF_Zero, BO_Add, BF_One, BF_Zero>::GetRHI(); + } + } + else + { + if (bNoAlphaWrite) + { + GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGB>::GetRHI(); + } + else + { + // for mirror window, write RGBA, RGB = src.rgb * src.a + dst.rgb * (1 - src.a), A = src.a * 1 + dst.a * (1 - src a) + GraphicsPSOInit.BlendState = TStaticBlendState<CW_RGBA, BO_Add, BF_SourceAlpha, BF_InverseSourceAlpha, BO_Add, BF_One, BF_InverseSourceAlpha>::GetRHI(); + } + } + + GraphicsPSOInit.RasterizerState = TStaticRasterizerState<>::GetRHI(); + GraphicsPSOInit.DepthStencilState = TStaticDepthStencilState<false, CF_Always>::GetRHI(); + GraphicsPSOInit.PrimitiveType = PT_TriangleList; + + const auto FeatureLevel = GMaxRHIFeatureLevel; + auto ShaderMap = GetGlobalShaderMap(FeatureLevel); + TShaderMapRef<FScreenVS> VertexShader(ShaderMap); + GraphicsPSOInit.BoundShaderState.VertexDeclarationRHI = GFilterVertexDeclaration.VertexDeclarationRHI; + GraphicsPSOInit.BoundShaderState.VertexShaderRHI = VertexShader.GetVertexShader(); + + if (DstTexture2D) + { + sRGBSource &= EnumHasAnyFlags(SrcTexture->GetFlags(), TexCreate_SRGB); + + // Need to copy over mip maps on Android since they are not generated like they are on PC +#if PLATFORM_ANDROID + uint32 NumMips = SrcTexture->GetNumMips(); +#else + uint32 NumMips = 1; +#endif + + for (uint32 MipIndex = 0; MipIndex < NumMips; MipIndex++) + { + FRHIRenderPassInfo RPInfo(DstTexture, ERenderTargetActions::Load_Store); + RPInfo.ColorRenderTargets[0].MipIndex = MipIndex; + + RHICmdList.BeginRenderPass(RPInfo, TEXT("CopyTexture")); + { + const uint32 MipViewportWidth = ViewportWidth >> MipIndex; + const uint32 MipViewportHeight = ViewportHeight >> MipIndex; + const FIntPoint MipTargetSize(MipViewportWidth, MipViewportHeight); + + if (bNoAlphaWrite || bInvertAlpha) + { + RHICmdList.SetViewport(DstRect.Min.X, DstRect.Min.Y, 0.0f, DstRect.Max.X, DstRect.Max.Y, 1.0f); + DrawClearQuad(RHICmdList, bAlphaPremultiply ? FLinearColor::Black : FLinearColor::White); + } + + RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); + FRHISamplerState* SamplerState = DstRect.Size() == SrcRect.Size() ? TStaticSamplerState<SF_Point>::GetRHI() : TStaticSamplerState<SF_Bilinear>::GetRHI(); + + if (!sRGBSource) + { + TShaderMapRef<FScreenPSMipLevel> PixelShader(ShaderMap); + GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); + SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); + PixelShader->SetParameters(RHICmdList, SamplerState, SrcTextureRHI, MipIndex); + } + else + { + TShaderMapRef<FScreenPSsRGBSourceMipLevel> PixelShader(ShaderMap); + GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); + SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); + PixelShader->SetParameters(RHICmdList, SamplerState, SrcTextureRHI, MipIndex); + } + + RHICmdList.SetViewport(DstRect.Min.X, DstRect.Min.Y, 0.0f, DstRect.Min.X + MipViewportWidth, DstRect.Min.Y + MipViewportHeight, 1.0f); + + RendererModule->DrawRectangle( + RHICmdList, + 0, 0, MipViewportWidth, MipViewportHeight, + U, V, USize, VSize, + MipTargetSize, + FIntPoint(1, 1), + VertexShader, + EDRF_Default); + } + RHICmdList.EndRenderPass(); + } + } + else + { + for (int FaceIndex = 0; FaceIndex < 6; FaceIndex++) + { + FRHIRenderPassInfo RPInfo(DstTexture, ERenderTargetActions::Load_Store); + + // On Vulkan the positive and negative Y faces of the cubemap need to be flipped + if (RenderAPI == ovrpRenderAPI_Vulkan) + { + int NewFaceIndex = 0; + + if (FaceIndex == VULKAN_CUBEMAP_POSITIVE_Y) + NewFaceIndex = VULKAN_CUBEMAP_NEGATIVE_Y; + else if (FaceIndex == VULKAN_CUBEMAP_NEGATIVE_Y) + NewFaceIndex = VULKAN_CUBEMAP_POSITIVE_Y; + else + NewFaceIndex = FaceIndex; + + RPInfo.ColorRenderTargets[0].ArraySlice = NewFaceIndex; + } + else + { + RPInfo.ColorRenderTargets[0].ArraySlice = FaceIndex; + } + + RHICmdList.BeginRenderPass(RPInfo, TEXT("CopyTextureFace")); + { + if (bNoAlphaWrite) + { + DrawClearQuad(RHICmdList, bAlphaPremultiply ? FLinearColor::Black : FLinearColor::White); + } + + RHICmdList.ApplyCachedRenderTargets(GraphicsPSOInit); + + TShaderMapRef<FOculusCubemapPS> PixelShader(ShaderMap); + GraphicsPSOInit.BoundShaderState.PixelShaderRHI = PixelShader.GetPixelShader(); + SetGraphicsPipelineState(RHICmdList, GraphicsPSOInit, 0); + FRHISamplerState* SamplerState = DstRect.Size() == SrcRect.Size() ? TStaticSamplerState<SF_Point>::GetRHI() : TStaticSamplerState<SF_Bilinear>::GetRHI(); + PixelShader->SetParameters(RHICmdList, SamplerState, SrcTextureRHI, FaceIndex); + + RHICmdList.SetViewport(DstRect.Min.X, DstRect.Min.Y, 0.0f, DstRect.Max.X, DstRect.Max.Y, 1.0f); + + RendererModule->DrawRectangle( + RHICmdList, + 0, 0, ViewportWidth, ViewportHeight, +#if PLATFORM_ANDROID + U, V, USize, VSize, +#else + U, 1.0 - V, USize, -VSize, +#endif + TargetSize, + FIntPoint(1, 1), + VertexShader, + EDRF_Default); + } + RHICmdList.EndRenderPass(); + } + } +} + +void FCustomPresent::SubmitGPUCommands_RenderThread(FRHICommandListImmediate& RHICmdList) +{ + CheckInRenderThread(); + + RHICmdList.SubmitCommandsHint(); +} + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent.h new file mode 100644 index 0000000000000000000000000000000000000000..4fc5ef2677cc4f1e3b441ee3462b4e565c681935 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent.h @@ -0,0 +1,111 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD_Settings.h" +#include "OculusXRHMD_GameFrame.h" +#include "XRSwapChain.h" +#include "RHI.h" +#include "RendererInterface.h" +#include "IStereoLayers.h" +#include "XRRenderBridge.h" + +#if PLATFORM_WINDOWS +#include "Windows/WindowsHWrapper.h" +#endif + +DECLARE_STATS_GROUP(TEXT("OculusXRHMD"), STATGROUP_OculusXRHMD, STATCAT_Advanced); +DECLARE_CYCLE_STAT(TEXT("BeginRendering"), STAT_BeginRendering, STATGROUP_OculusXRHMD); +DECLARE_CYCLE_STAT(TEXT("FinishRendering"), STAT_FinishRendering, STATGROUP_OculusXRHMD); +DECLARE_FLOAT_COUNTER_STAT(TEXT("LatencyRender"), STAT_LatencyRender, STATGROUP_OculusXRHMD); +DECLARE_FLOAT_COUNTER_STAT(TEXT("LatencyTimewarp"), STAT_LatencyTimewarp, STATGROUP_OculusXRHMD); +DECLARE_FLOAT_COUNTER_STAT(TEXT("LatencyPostPresent"), STAT_LatencyPostPresent, STATGROUP_OculusXRHMD); +DECLARE_FLOAT_COUNTER_STAT(TEXT("ErrorRender"), STAT_ErrorRender, STATGROUP_OculusXRHMD); +DECLARE_FLOAT_COUNTER_STAT(TEXT("ErrorTimewarp"), STAT_ErrorTimewarp, STATGROUP_OculusXRHMD); + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FCustomPresent +//------------------------------------------------------------------------------------------------- + +class FCustomPresent : public FXRRenderBridge +{ +public: + FCustomPresent(class FOculusXRHMD* InOculusXRHMD, ovrpRenderAPIType InRenderAPI, EPixelFormat InDefaultPixelFormat, bool InSupportsSRGB); + + // FXRRenderBridge/FRHICustomPresent + virtual bool NeedsNativePresent() override; + virtual bool Present(int32& SyncInterval) override; + virtual void FinishRendering_RHIThread(); + + ovrpRenderAPIType GetRenderAPI() const { return RenderAPI; } + virtual bool IsUsingCorrectDisplayAdapter() const { return true; } + + void UpdateMirrorTexture_RenderThread(); + void ReleaseResources_RHIThread(); + void Shutdown(); + + FTexture2DRHIRef GetMirrorTexture() { return MirrorTextureRHI; } + + virtual void* GetOvrpInstance() const { return nullptr; } + virtual void* GetOvrpPhysicalDevice() const { return nullptr; } + virtual void* GetOvrpDevice() const { return nullptr; } + virtual void* GetOvrpCommandQueue() const { return nullptr; } + EPixelFormat GetPixelFormat(EPixelFormat InFormat) const; + EPixelFormat GetPixelFormat(ovrpTextureFormat InFormat) const; + EPixelFormat GetDefaultPixelFormat() const { return DefaultPixelFormat; } + ovrpTextureFormat GetOvrpTextureFormat(EPixelFormat InFormat, bool usesRGB = true) const; + ovrpTextureFormat GetDefaultOvrpTextureFormat() const { return DefaultOvrpTextureFormat; } + ovrpTextureFormat GetDefaultDepthOvrpTextureFormat() const { return DefaultDepthOvrpTextureFormat; } + static bool IsSRGB(ovrpTextureFormat InFormat); + virtual int GetSystemRecommendedMSAALevel() const; + virtual int GetLayerFlags() const { return 0; } + + virtual FTextureRHIRef CreateTexture_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, ovrpTextureHandle InTexture, ETextureCreateFlags TexCreateFlags) = 0; + FXRSwapChainPtr CreateSwapChain_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, const TArray<ovrpTextureHandle>& InTextures, ETextureCreateFlags InTexCreateFlags, const TCHAR* DebugName); + void CopyTexture_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture* DstTexture, FRHITexture* SrcTexture, FIntRect DstRect = FIntRect(), FIntRect SrcRect = FIntRect(), bool bAlphaPremultiply = false, bool bNoAlphaWrite = false, bool bInvertY = true, bool sRGBSource = false, bool bInvertAlpha = false) const; + void SubmitGPUCommands_RenderThread(FRHICommandListImmediate& RHICmdList); + virtual void SubmitGPUFrameTime(float GPUFrameTime) { } +#ifdef WITH_OCULUS_BRANCH + virtual void UpdateFoveationOffsets_RHIThread(bool bUseOffsets, FIntPoint Offsets[2]) { }; +#endif // WITH_OCULUS_BRANCH + + bool SupportsSRGB() { return bSupportsSRGB; } + bool SupportsSubsampled() { return bSupportsSubsampled; } + +protected: + FOculusXRHMD* OculusXRHMD; + ovrpRenderAPIType RenderAPI; + EPixelFormat DefaultPixelFormat; + bool bSupportsSRGB; + bool bSupportsSubsampled; + ovrpTextureFormat DefaultOvrpTextureFormat; + ovrpTextureFormat DefaultDepthOvrpTextureFormat; + IRendererModule* RendererModule; + FTexture2DRHIRef MirrorTextureRHI; + bool bIsStandaloneStereoDevice; +}; + + +//------------------------------------------------------------------------------------------------- +// APIs +//------------------------------------------------------------------------------------------------- + +#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 +FCustomPresent* CreateCustomPresent_D3D11(FOculusXRHMD* InOculusXRHMD); +#endif +#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 +FCustomPresent* CreateCustomPresent_D3D12(FOculusXRHMD* InOculusXRHMD); +#endif +#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN +FCustomPresent* CreateCustomPresent_Vulkan(FOculusXRHMD* InOculusXRHMD); +#endif + + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_D3D11.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_D3D11.cpp new file mode 100644 index 0000000000000000000000000000000000000000..44b8cb1ddedc816fdaaed1abd8724daa78d2125f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_D3D11.cpp @@ -0,0 +1,125 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_CustomPresent.h" +#include "OculusXRHMDPrivateRHI.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 +#include "OculusXRHMD.h" + +#ifndef WINDOWS_PLATFORM_TYPES_GUARD +#include "Windows/AllowWindowsPlatformTypes.h" +#endif + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FD3D11CustomPresent +//------------------------------------------------------------------------------------------------- + +class FD3D11CustomPresent : public FCustomPresent +{ +public: + FD3D11CustomPresent(FOculusXRHMD* InOculusXRHMD); + + // Implementation of FCustomPresent, called by Plugin itself + virtual bool IsUsingCorrectDisplayAdapter() const override; + virtual void* GetOvrpDevice() const override; + virtual FTextureRHIRef CreateTexture_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, ovrpTextureHandle InTexture, ETextureCreateFlags InTexCreateFlags) override; +}; + + +FD3D11CustomPresent::FD3D11CustomPresent(FOculusXRHMD* InOculusXRHMD) : + FCustomPresent(InOculusXRHMD, ovrpRenderAPI_D3D11, PF_B8G8R8A8, true) +{ + switch (GPixelFormats[PF_DepthStencil].PlatformFormat) + { + case DXGI_FORMAT_R24G8_TYPELESS: + DefaultDepthOvrpTextureFormat = ovrpTextureFormat_D24_S8; + break; + case DXGI_FORMAT_R32G8X24_TYPELESS: + DefaultDepthOvrpTextureFormat = ovrpTextureFormat_D32_FP_S8; + break; + default: + UE_LOG(LogHMD, Error, TEXT("Unrecognized depth buffer format")); + break; + } +} + + +bool FD3D11CustomPresent::IsUsingCorrectDisplayAdapter() const +{ + const void* luid; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetDisplayAdapterId2(&luid)) && luid) + { + TRefCountPtr<ID3D11Device> D3D11Device; + + ExecuteOnRenderThread([&D3D11Device]() + { + D3D11Device = (ID3D11Device*) RHIGetNativeDevice(); + }); + + if (D3D11Device) + { + TRefCountPtr<IDXGIDevice> DXGIDevice; + TRefCountPtr<IDXGIAdapter> DXGIAdapter; + DXGI_ADAPTER_DESC DXGIAdapterDesc; + + if (SUCCEEDED(D3D11Device->QueryInterface(__uuidof(IDXGIDevice), (void**) DXGIDevice.GetInitReference())) && + SUCCEEDED(DXGIDevice->GetAdapter(DXGIAdapter.GetInitReference())) && + SUCCEEDED(DXGIAdapter->GetDesc(&DXGIAdapterDesc))) + { + return !FMemory::Memcmp(luid, &DXGIAdapterDesc.AdapterLuid, sizeof(LUID)); + } + } + } + + // Not enough information. Assume that we are using the correct adapter. + return true; +} + + +void* FD3D11CustomPresent::GetOvrpDevice() const +{ + return GetID3D11DynamicRHI()->RHIGetDevice(); +} + + +FTextureRHIRef FD3D11CustomPresent::CreateTexture_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, ovrpTextureHandle InTexture, ETextureCreateFlags InTexCreateFlags) +{ + CheckInRenderThread(); + + switch (InResourceType) + { + case RRT_Texture2D: + return GetID3D11DynamicRHI()->RHICreateTexture2DFromResource(InFormat, InTexCreateFlags, InBinding, (ID3D11Texture2D*)InTexture).GetReference(); + + case RRT_Texture2DArray: + return GetID3D11DynamicRHI()->RHICreateTexture2DArrayFromResource(InFormat, InTexCreateFlags, InBinding, (ID3D11Texture2D*)InTexture).GetReference(); + + case RRT_TextureCube: + return GetID3D11DynamicRHI()->RHICreateTextureCubeFromResource(InFormat, InTexCreateFlags | TexCreate_TargetArraySlicesIndependently, InBinding, (ID3D11Texture2D*)InTexture).GetReference(); + + default: + return nullptr; + } +} + +//------------------------------------------------------------------------------------------------- +// APIs +//------------------------------------------------------------------------------------------------- + +FCustomPresent* CreateCustomPresent_D3D11(FOculusXRHMD* InOculusXRHMD) +{ + return new FD3D11CustomPresent(InOculusXRHMD); +} + + +} // namespace OculusXRHMD + +#if PLATFORM_WINDOWS +#undef WINDOWS_PLATFORM_TYPES_GUARD +#endif + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS_D3D11 diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_D3D12.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_D3D12.cpp new file mode 100644 index 0000000000000000000000000000000000000000..4aba07877c7309d8e7ba2f08e4a8e932c63b97fe --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_D3D12.cpp @@ -0,0 +1,119 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_CustomPresent.h" +#include "OculusXRHMDPrivateRHI.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 +#include "OculusXRHMD.h" + +#ifndef WINDOWS_PLATFORM_TYPES_GUARD +#include "Windows/AllowWindowsPlatformTypes.h" +#endif + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FCustomPresentD3D12 +//------------------------------------------------------------------------------------------------- + +class FD3D12CustomPresent : public FCustomPresent +{ +public: + FD3D12CustomPresent(FOculusXRHMD* InOculusXRHMD); + + // Implementation of FCustomPresent, called by Plugin itself + virtual bool IsUsingCorrectDisplayAdapter() const override; + virtual void* GetOvrpDevice() const override; + virtual FTextureRHIRef CreateTexture_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, ovrpTextureHandle InTexture, ETextureCreateFlags InTexCreateFlags) override; +}; + + +FD3D12CustomPresent::FD3D12CustomPresent(FOculusXRHMD* InOculusXRHMD) : + FCustomPresent(InOculusXRHMD, ovrpRenderAPI_D3D12, PF_B8G8R8A8, true) +{ + switch (GPixelFormats[PF_DepthStencil].PlatformFormat) + { + case DXGI_FORMAT_R24G8_TYPELESS: + DefaultDepthOvrpTextureFormat = ovrpTextureFormat_D24_S8; + break; + case DXGI_FORMAT_R32G8X24_TYPELESS: + DefaultDepthOvrpTextureFormat = ovrpTextureFormat_D32_FP_S8; + break; + default: + UE_LOG(LogHMD, Error, TEXT("Unrecognized depth buffer format")); + break; + } +} + + +bool FD3D12CustomPresent::IsUsingCorrectDisplayAdapter() const +{ + const void* luid; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetDisplayAdapterId2(&luid)) && luid) + { + TRefCountPtr<ID3D12Device> D3DDevice; + + ExecuteOnRenderThread([&D3DDevice]() + { + D3DDevice = (ID3D12Device*) RHIGetNativeDevice(); + }); + + if (D3DDevice) + { + LUID AdapterLuid = D3DDevice->GetAdapterLuid(); + return !FMemory::Memcmp(luid, &AdapterLuid, sizeof(LUID)); + } + } + + // Not enough information. Assume that we are using the correct adapter. + return true; +} + +void* FD3D12CustomPresent::GetOvrpDevice() const +{ + return GetID3D12DynamicRHI()->RHIGetCommandQueue(); +} + + +FTextureRHIRef FD3D12CustomPresent::CreateTexture_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, ovrpTextureHandle InTexture, ETextureCreateFlags InTexCreateFlags) +{ + CheckInRenderThread(); + + ID3D12DynamicRHI* DynamicRHI = GetID3D12DynamicRHI(); + + switch (InResourceType) + { + case RRT_Texture2D: + return DynamicRHI->RHICreateTexture2DFromResource(InFormat, InTexCreateFlags, InBinding, (ID3D12Resource*) InTexture).GetReference(); + + case RRT_Texture2DArray: + return DynamicRHI->RHICreateTexture2DArrayFromResource(InFormat, InTexCreateFlags, InBinding, (ID3D12Resource*)InTexture).GetReference(); + + case RRT_TextureCube: + return DynamicRHI->RHICreateTextureCubeFromResource(InFormat, InTexCreateFlags, InBinding, (ID3D12Resource*) InTexture).GetReference(); + + default: + return nullptr; + } +} + + +//------------------------------------------------------------------------------------------------- +// APIs +//------------------------------------------------------------------------------------------------- + +FCustomPresent* CreateCustomPresent_D3D12(FOculusXRHMD* InOculusXRHMD) +{ + return new FD3D12CustomPresent(InOculusXRHMD); +} + + +} // namespace OculusXRHMD + +#if PLATFORM_WINDOWS +#undef WINDOWS_PLATFORM_TYPES_GUARD +#endif + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS_D3D12 diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_Vulkan.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_Vulkan.cpp new file mode 100644 index 0000000000000000000000000000000000000000..28a1a147887d75bca87ebd1df7ce6f8d1c317b15 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_CustomPresent_Vulkan.cpp @@ -0,0 +1,168 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_CustomPresent.h" +#include "OculusXRHMDPrivateRHI.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN +#include "OculusXRHMD.h" +#include "IVulkanDynamicRHI.h" + +#if PLATFORM_WINDOWS +#ifndef WINDOWS_PLATFORM_TYPES_GUARD +#include "Windows/AllowWindowsPlatformTypes.h" +#endif +#endif + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FCustomPresentVulkan +//------------------------------------------------------------------------------------------------- + +class FVulkanCustomPresent : public FCustomPresent +{ +public: + FVulkanCustomPresent(FOculusXRHMD* InOculusXRHMD); + + // Implementation of FCustomPresent, called by Plugin itself + virtual bool IsUsingCorrectDisplayAdapter() const override; + virtual void* GetOvrpInstance() const override; + virtual void* GetOvrpPhysicalDevice() const override; + virtual void* GetOvrpDevice() const override; + virtual void* GetOvrpCommandQueue() const override; + virtual FTextureRHIRef CreateTexture_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, ovrpTextureHandle InTexture, ETextureCreateFlags InTexCreateFlags) override; +#ifdef WITH_OCULUS_BRANCH + virtual void UpdateFoveationOffsets_RHIThread(bool bUseTileOffsets, FIntPoint TileOffsets[2]) override; +#endif // WITH_OCULUS_BRANCH +}; + + +FVulkanCustomPresent::FVulkanCustomPresent(FOculusXRHMD* InOculusXRHMD) : + FCustomPresent(InOculusXRHMD, ovrpRenderAPI_Vulkan, PF_R8G8B8A8, true) +{ +#if PLATFORM_ANDROID + if (GRHISupportsRHIThread && GIsThreadedRendering && GUseRHIThread_InternalUseOnly) + { + SetRHIThreadEnabled(false, false); + } +#endif + +#if PLATFORM_WINDOWS +/* + switch (GPixelFormats[PF_DepthStencil].PlatformFormat) + { + case VK_FORMAT_D24_UNORM_S8_UINT: + DefaultDepthOvrpTextureFormat = ovrpTextureFormat_D24_S8; + break; + case VK_FORMAT_D32_SFLOAT_S8_UINT: + DefaultDepthOvrpTextureFormat = ovrpTextureFormat_D32_S824_FP; + break; + default: + UE_LOG(LogHMD, Error, TEXT("Unrecognized depth buffer format")); + break; + } +*/ +#endif + bSupportsSubsampled = GetIVulkanDynamicRHI()->RHISupportsEXTFragmentDensityMap2(); +} + + +bool FVulkanCustomPresent::IsUsingCorrectDisplayAdapter() const +{ +#if PLATFORM_WINDOWS + const void* AdapterId = nullptr; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetDisplayAdapterId2(&AdapterId)) && AdapterId) + { + return GetIVulkanDynamicRHI()->RHIDoesAdapterMatchDevice(AdapterId); + } +#endif + + // Not enough information. Assume that we are using the correct adapter. + return true; +} + + +void* FVulkanCustomPresent::GetOvrpInstance() const +{ + return GetIVulkanDynamicRHI()->RHIGetVkInstance(); +} + + +void* FVulkanCustomPresent::GetOvrpPhysicalDevice() const +{ + return GetIVulkanDynamicRHI()->RHIGetVkPhysicalDevice(); +} + + +void* FVulkanCustomPresent::GetOvrpDevice() const +{ + return GetIVulkanDynamicRHI()->RHIGetVkDevice(); +} + + +void* FVulkanCustomPresent::GetOvrpCommandQueue() const +{ + return GetIVulkanDynamicRHI()->RHIGetGraphicsVkQueue(); +} + + +FTextureRHIRef FVulkanCustomPresent::CreateTexture_RenderThread(uint32 InSizeX, uint32 InSizeY, EPixelFormat InFormat, FClearValueBinding InBinding, uint32 InNumMips, uint32 InNumSamples, uint32 InNumSamplesTileMem, ERHIResourceType InResourceType, ovrpTextureHandle InTexture, ETextureCreateFlags InTexCreateFlags) +{ + CheckInRenderThread(); + + IVulkanDynamicRHI* VulkanRHI = GetIVulkanDynamicRHI(); + const VkImageSubresourceRange SubresourceRangeAll = { VK_IMAGE_ASPECT_COLOR_BIT, 0, VK_REMAINING_MIP_LEVELS, 0, VK_REMAINING_ARRAY_LAYERS }; + + if (EnumHasAnyFlags(InTexCreateFlags,TexCreate_RenderTargetable)) + { + VulkanRHI->RHISetImageLayout((VkImage)InTexture, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, SubresourceRangeAll); + } + else if (EnumHasAnyFlags(InTexCreateFlags,TexCreate_Foveation)) + { + VulkanRHI->RHISetImageLayout((VkImage)InTexture, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_FRAGMENT_DENSITY_MAP_OPTIMAL_EXT, SubresourceRangeAll); + } + + switch (InResourceType) + { + case RRT_Texture2D: + return VulkanRHI->RHICreateTexture2DFromResource(InFormat, InSizeX, InSizeY, InNumMips, InNumSamples, (VkImage) InTexture, InTexCreateFlags).GetReference(); + + case RRT_Texture2DArray: + return VulkanRHI->RHICreateTexture2DArrayFromResource(InFormat, InSizeX, InSizeY, 2, InNumMips, InNumSamples, (VkImage) InTexture, InTexCreateFlags).GetReference(); + + case RRT_TextureCube: + return VulkanRHI->RHICreateTextureCubeFromResource(InFormat, InSizeX, false, 1, InNumMips, (VkImage) InTexture, InTexCreateFlags).GetReference(); + + default: + return nullptr; + } +} + +#ifdef WITH_OCULUS_BRANCH +void FVulkanCustomPresent::UpdateFoveationOffsets_RHIThread(bool bUseOffsets, FIntPoint Offsets[2]) +{ + CheckInRHIThread(); + + SCOPED_NAMED_EVENT(UpdateFoveationOffsets_RHIThread, FColor::Red); + GetIVulkanDynamicRHI()->RHISetQcomFragmentDensityMapOffsets(bUseOffsets, Offsets); +} +#endif // WITH_OCULUS_BRANCH + +//------------------------------------------------------------------------------------------------- +// APIs +//------------------------------------------------------------------------------------------------- + +FCustomPresent* CreateCustomPresent_Vulkan(FOculusXRHMD* InOculusXRHMD) +{ + return new FVulkanCustomPresent(InOculusXRHMD); +} + + +} // namespace OculusXRHMD + +#if PLATFORM_WINDOWS +#undef WINDOWS_PLATFORM_TYPES_GUARD +#endif + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DeferredDeletionQueue.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DeferredDeletionQueue.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7229427424b81fea51157c88f07895d633cb2af6 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DeferredDeletionQueue.cpp @@ -0,0 +1,72 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_DeferredDeletionQueue.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMDPrivate.h" +#include "XRThreadUtils.h" +#include "OculusXRHMDModule.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FDeferredDeletionQueue +//------------------------------------------------------------------------------------------------- +uint32 GOculusXRHMDLayerDeletionFrameNumber = 0; +const uint32 NUM_FRAMES_TO_WAIT_FOR_LAYER_DELETE = 3; +const uint32 NUM_FRAMES_TO_WAIT_FOR_OVRP_LAYER_DELETE = 7; + +void FDeferredDeletionQueue::AddLayerToDeferredDeletionQueue(const FLayerPtr& ptr) +{ + DeferredDeletionEntry Entry; + Entry.Layer = ptr; + Entry.FrameEnqueued = GOculusXRHMDLayerDeletionFrameNumber; + Entry.EntryType = DeferredDeletionEntry::DeferredDeletionEntryType::Layer; + DeferredDeletionArray.Add(Entry); +} + +void FDeferredDeletionQueue::AddOVRPLayerToDeferredDeletionQueue(const uint32 layerID) +{ + DeferredDeletionEntry Entry; + Entry.OvrpLayerId = layerID; + Entry.FrameEnqueued = GOculusXRHMDLayerDeletionFrameNumber; + Entry.EntryType = DeferredDeletionEntry::DeferredDeletionEntryType::OvrpLayer; + DeferredDeletionArray.Add(Entry); +} + +void FDeferredDeletionQueue::HandleLayerDeferredDeletionQueue_RenderThread(bool bDeleteImmediately) +{ + // Traverse list backwards so the swap switches to elements already tested + for (int32 Index = DeferredDeletionArray.Num() - 1; Index >= 0; --Index) + { + DeferredDeletionEntry* Entry = &DeferredDeletionArray[Index]; + if (Entry->EntryType == DeferredDeletionEntry::DeferredDeletionEntryType::Layer) + { + if (bDeleteImmediately || GOculusXRHMDLayerDeletionFrameNumber > Entry->FrameEnqueued + NUM_FRAMES_TO_WAIT_FOR_LAYER_DELETE) + { + DeferredDeletionArray.RemoveAtSwap(Index, 1, false); + } + } + else if (Entry->EntryType == DeferredDeletionEntry::DeferredDeletionEntryType::OvrpLayer) + { + if (bDeleteImmediately || GOculusXRHMDLayerDeletionFrameNumber > Entry->FrameEnqueued + NUM_FRAMES_TO_WAIT_FOR_OVRP_LAYER_DELETE) + { + ExecuteOnRHIThread_DoNotWait([OvrpLayerId = Entry->OvrpLayerId]() + { + UE_LOG(LogHMD, Warning, TEXT("Destroying layer %d"), OvrpLayerId); + FOculusXRHMDModule::GetPluginWrapper().DestroyLayer(OvrpLayerId); + }); + DeferredDeletionArray.RemoveAtSwap(Index, 1, false); + } + } + + } + + // if the function is to be called multiple times, move this increment somewhere unique! + ++GOculusXRHMDLayerDeletionFrameNumber; +} + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DeferredDeletionQueue.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DeferredDeletionQueue.h new file mode 100644 index 0000000000000000000000000000000000000000..f4f7da555c0bdd030c58631be684783787dc3ae1 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DeferredDeletionQueue.h @@ -0,0 +1,44 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD_Layer.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FDeferredDeletionQueue +//------------------------------------------------------------------------------------------------- + +class FDeferredDeletionQueue +{ +public: + void AddLayerToDeferredDeletionQueue(const FLayerPtr& ptr); + void AddOVRPLayerToDeferredDeletionQueue(const uint32 layerID); + void HandleLayerDeferredDeletionQueue_RenderThread(bool bDeleteImmediately = false); + +private: + struct DeferredDeletionEntry + { + enum class DeferredDeletionEntryType + { + Layer, + OvrpLayer + }; + + FLayerPtr Layer; + uint32 OvrpLayerId; + + uint32 FrameEnqueued; + DeferredDeletionEntryType EntryType; + }; + + TArray<DeferredDeletionEntry> DeferredDeletionArray; +}; + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DynamicResolutionState.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DynamicResolutionState.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8b2b8e55e1dfee1d8eed9bf1f000769da2f57af7 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DynamicResolutionState.cpp @@ -0,0 +1,106 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_DynamicResolutionState.h" +#include "LegacyScreenPercentageDriver.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "SceneView.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FDynamicResolutionState implementation +//------------------------------------------------------------------------------------------------- + +FDynamicResolutionState::FDynamicResolutionState(const OculusXRHMD::FSettingsPtr InSettings) + : Settings(InSettings) + , ResolutionFraction(-1.0f) + , ResolutionFractionUpperBound(-1.0f) +{ + check(Settings.IsValid()); +} + +void FDynamicResolutionState::ResetHistory() +{ + // Empty - Oculus drives resolution fraction externally +}; + +bool FDynamicResolutionState::IsSupported() const +{ + return true; +} + +void FDynamicResolutionState::SetupMainViewFamily(class FSceneViewFamily& ViewFamily) +{ + check(IsInGameThread()); + check(ViewFamily.EngineShowFlags.ScreenPercentage == true); + + if (ViewFamily.Views.Num() > 0 && IsEnabled()) + { + // We can assume both eyes have the same fraction + const FSceneView& View = *ViewFamily.Views[0]; + check(View.UnconstrainedViewRect == View.UnscaledViewRect); + + // Compute desired resolution fraction range + float MinResolutionFraction = Settings->PixelDensityMin; + float MaxResolutionFraction = Settings->PixelDensityMax; + + // Clamp resolution fraction to what the renderer can do. + MinResolutionFraction = FMath::Max(MinResolutionFraction, ISceneViewFamilyScreenPercentage::kMinResolutionFraction); + MaxResolutionFraction = FMath::Min(MaxResolutionFraction, ISceneViewFamilyScreenPercentage::kMaxResolutionFraction); + + if (View.AntiAliasingMethod == AAM_TSR) + { + MinResolutionFraction = FMath::Max(MinResolutionFraction, ISceneViewFamilyScreenPercentage::kMinTSRResolutionFraction); + MaxResolutionFraction = FMath::Min(MaxResolutionFraction, ISceneViewFamilyScreenPercentage::kMaxTSRResolutionFraction); + } + else if (View.AntiAliasingMethod == AAM_TemporalAA) + { + MinResolutionFraction = FMath::Max(MinResolutionFraction, ISceneViewFamilyScreenPercentage::kMinTAAUpsampleResolutionFraction); + MaxResolutionFraction = FMath::Min(MaxResolutionFraction, ISceneViewFamilyScreenPercentage::kMaxTAAUpsampleResolutionFraction); + } + + ResolutionFraction = FMath::Clamp(Settings->PixelDensity, MinResolutionFraction, MaxResolutionFraction); + ResolutionFractionUpperBound = MaxResolutionFraction; + + ViewFamily.SetScreenPercentageInterface(new FLegacyScreenPercentageDriver(ViewFamily, ResolutionFraction, ResolutionFractionUpperBound)); + } +} + +DynamicRenderScaling::TMap<float> FDynamicResolutionState::GetResolutionFractionsApproximation() const +{ + DynamicRenderScaling::TMap<float> ResolutionFractions; + ResolutionFractions.SetAll(1.0f); + ResolutionFractions[GDynamicPrimaryResolutionFraction] = ResolutionFraction; + return ResolutionFractions; +} + +DynamicRenderScaling::TMap<float> FDynamicResolutionState::GetResolutionFractionsUpperBound() const +{ + DynamicRenderScaling::TMap<float> ResolutionFractions; + ResolutionFractions.SetAll(1.0f); + ResolutionFractions[GDynamicPrimaryResolutionFraction] = ResolutionFractionUpperBound; + return ResolutionFractionUpperBound; +} + +void FDynamicResolutionState::SetEnabled(bool bEnable) +{ + check(IsInGameThread()); + Settings->Flags.bPixelDensityAdaptive = bEnable; +} + +bool FDynamicResolutionState::IsEnabled() const +{ + check(IsInGameThread()); + return Settings->Flags.bPixelDensityAdaptive; +} + +void FDynamicResolutionState::ProcessEvent(EDynamicResolutionStateEvent Event) +{ + // Empty - Oculus drives resolution fraction externally +}; + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DynamicResolutionState.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DynamicResolutionState.h new file mode 100644 index 0000000000000000000000000000000000000000..c6c59c5129e622231f4df59c270f30da9a9a3257 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_DynamicResolutionState.h @@ -0,0 +1,42 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD_Settings.h" +#include "DynamicResolutionState.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FDynamicResolutionState +//------------------------------------------------------------------------------------------------- + +class FDynamicResolutionState : public IDynamicResolutionState +{ +public: + FDynamicResolutionState(const OculusXRHMD::FSettingsPtr InSettings); + + // ISceneViewFamilyScreenPercentage + virtual void ResetHistory() override; + virtual bool IsSupported() const override; + virtual void SetupMainViewFamily(class FSceneViewFamily& ViewFamily) override; + +protected: + virtual DynamicRenderScaling::TMap<float> GetResolutionFractionsApproximation() const override; + virtual DynamicRenderScaling::TMap<float> GetResolutionFractionsUpperBound() const override; + virtual void SetEnabled(bool bEnable) override; + virtual bool IsEnabled() const override; + virtual void ProcessEvent(EDynamicResolutionStateEvent Event) override; + +private: + const OculusXRHMD::FSettingsPtr Settings; + float ResolutionFraction; + float ResolutionFractionUpperBound; +}; + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_GameFrame.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_GameFrame.cpp new file mode 100644 index 0000000000000000000000000000000000000000..287510644a5183244e4349b93f48c469e9922d49 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_GameFrame.cpp @@ -0,0 +1,40 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_GameFrame.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "GameFramework/WorldSettings.h" +#include "Engine/Engine.h" +#include "Engine/World.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FGameFrame +//------------------------------------------------------------------------------------------------- + +FGameFrame::FGameFrame() : + FrameNumber(0), + WorldToMetersScale(100.f), + ShowFlags(ESFIM_All0), + PlayerOrientation(FQuat::Identity), + PlayerLocation(FVector::ZeroVector), + FoveatedRenderingMethod(EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering), + FoveatedRenderingLevel(EOculusXRFoveatedRenderingLevel::Off), + bDynamicFoveatedRendering(false) +{ + Flags.Raw = 0; + Fov[0] = Fov[1] = ovrpFovf{0,0,0,0}; +} + +TSharedPtr<FGameFrame, ESPMode::ThreadSafe> FGameFrame::Clone() const +{ + TSharedPtr<FGameFrame, ESPMode::ThreadSafe> NewFrame = MakeShareable(new FGameFrame(*this)); + return NewFrame; +} + + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_GameFrame.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_GameFrame.h new file mode 100644 index 0000000000000000000000000000000000000000..a0ebabd310312da261990c4f88552aeb985fb4ba --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_GameFrame.h @@ -0,0 +1,62 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD_Settings.h" +#include "ShowFlags.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FGameFrame +//------------------------------------------------------------------------------------------------- + +class FGameFrame : public TSharedFromThis<FGameFrame, ESPMode::ThreadSafe> +{ +public: + uint32 FrameNumber; // current frame number. (StartGameFrame_GameThread) + float WorldToMetersScale; // World units (UU) to Meters scale. (OnStartGameFrame) + FVector2D WindowSize; // actual window size (StartGameFrame_GameThread) + FEngineShowFlags ShowFlags; // (PreRenderViewFamily_RenderThread) + + FQuat PlayerOrientation; // (CalculateStereoViewOffset) + FVector PlayerLocation; // (CalculateStereoViewOffset) + float NearClippingPlane; // (GetStereoProjectionMatrix) + + FTransform TrackingToWorld; // (OnEndGameFrame) + FTransform LastTrackingToWorld; // (OnEndGameFrame) + + EOculusXRFoveatedRenderingMethod FoveatedRenderingMethod; // OnStartGameFrame + EOculusXRFoveatedRenderingLevel FoveatedRenderingLevel; // OnStartGameFrame + bool bDynamicFoveatedRendering; // OnStartGameFrame + + ovrpFovf Fov[ovrpEye_Count]; // UpdateStereoRenderingParams + + union + { + struct + { + /** True, if splash is shown */ + uint64 bSplashIsShown : 1; + /** True, if spectator screen is active */ + uint64 bSpectatorScreenActive : 1; + /** True if the frame's positions have been updated on the render thread */ + uint64 bRTLateUpdateDone : 1; + }; + uint64 Raw; + } Flags; + +public: + FGameFrame(); + + TSharedPtr<FGameFrame, ESPMode::ThreadSafe> Clone() const; +}; + +typedef TSharedPtr<FGameFrame, ESPMode::ThreadSafe> FGameFramePtr; + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Layer.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Layer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fcac9089bdbcc3f7033c3fb8060bd1c5c3cb47ac --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Layer.cpp @@ -0,0 +1,1447 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_Layer.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +//#include "MediaTexture.h" +//#include "ScreenRendering.h" +//#include "ScenePrivate.h" +//#include "PostProcess/SceneFilterRendering.h" +#include "PostProcess/SceneRenderTargets.h" +#include "HeadMountedDisplayTypes.h" // for LogHMD +#include "OculusXRHMD.h" +#include "XRThreadUtils.h" +#include "Engine/RendererSettings.h" +#include "Engine/Texture2D.h" +#include "UObject/ConstructorHelpers.h" +#include "Materials/MaterialInterface.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Engine/GameEngine.h" +#include "Engine/Public/SceneUtils.h" +#include "OculusXRHMDPrivate.h" +#include "OculusXRHMDModule.h" +#include "OculusXRHMD_DeferredDeletionQueue.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FOvrpLayer +//------------------------------------------------------------------------------------------------- + +FOvrpLayer::FOvrpLayer(uint32 InOvrpLayerId, FDeferredDeletionQueue* InDeferredDeletion) : + OvrpLayerId(InOvrpLayerId), DeferredDeletion(InDeferredDeletion) +{ +} + + +FOvrpLayer::~FOvrpLayer() +{ + if (IsInGameThread()) + { + ExecuteOnRenderThread([OvrpLayerId = this->OvrpLayerId, DeferredDeletion = this->DeferredDeletion]() + { + DeferredDeletion->AddOVRPLayerToDeferredDeletionQueue(OvrpLayerId); + }); + } + else + { + DeferredDeletion->AddOVRPLayerToDeferredDeletionQueue(OvrpLayerId); + } +} + + +//------------------------------------------------------------------------------------------------- +// FLayer +//------------------------------------------------------------------------------------------------- + +FLayer::FLayer(uint32 InId, const IStereoLayers::FLayerDesc& InDesc) : + bNeedsTexSrgbCreate(false), + Id(InId), + OvrpLayerId(0), + bUpdateTexture(false), + bInvertY(false), + bHasDepth(false), + PokeAHoleComponentPtr(nullptr), + PokeAHoleActor(nullptr) +{ + FMemory::Memzero(OvrpLayerDesc); + FMemory::Memzero(OvrpLayerSubmit); + SetDesc(InDesc); +} + + +FLayer::FLayer(const FLayer& Layer) : + bNeedsTexSrgbCreate(Layer.bNeedsTexSrgbCreate), + Id(Layer.Id), + Desc(Layer.Desc), + OvrpLayerId(Layer.OvrpLayerId), + OvrpLayer(Layer.OvrpLayer), + SwapChain(Layer.SwapChain), + DepthSwapChain(Layer.DepthSwapChain), + FoveationSwapChain(Layer.FoveationSwapChain), + RightSwapChain(Layer.RightSwapChain), + RightDepthSwapChain(Layer.RightDepthSwapChain), + MotionVectorSwapChain(Layer.MotionVectorSwapChain), + MotionVectorDepthSwapChain(Layer.MotionVectorDepthSwapChain), + InvAlphaTexture(Layer.InvAlphaTexture), + bUpdateTexture(Layer.bUpdateTexture), + bInvertY(Layer.bInvertY), + bHasDepth(Layer.bHasDepth), + PokeAHoleComponentPtr(Layer.PokeAHoleComponentPtr), + PokeAHoleActor(Layer.PokeAHoleActor), + UserDefinedGeometryMap(Layer.UserDefinedGeometryMap), + PassthroughPokeActorMap(Layer.PassthroughPokeActorMap) +{ + FMemory::Memcpy(&OvrpLayerDesc, &Layer.OvrpLayerDesc, sizeof(OvrpLayerDesc)); + FMemory::Memcpy(&OvrpLayerSubmit, &Layer.OvrpLayerSubmit, sizeof(OvrpLayerSubmit)); +} + + +FLayer::~FLayer() +{ +} + + +void FLayer::SetDesc(const IStereoLayers::FLayerDesc& InDesc) +{ + if (Desc.Texture != InDesc.Texture || Desc.LeftTexture != InDesc.LeftTexture) + { + bUpdateTexture = true; + } + + Desc = InDesc; + + if (!UserDefinedGeometryMap) + { + UserDefinedGeometryMap = MakeShared<TMap<FString, FPassthroughMesh>, ESPMode::ThreadSafe>(); + } + + if (!PassthroughPokeActorMap) + { + PassthroughPokeActorMap = MakeShared<TMap<FString,FPassthroughPokeActor>, ESPMode::ThreadSafe>(); + } + + HandlePokeAHoleComponent(); + +#if !PLATFORM_ANDROID + // Mark all layers as supporting depth for now, due to artifacts with ovrpLayerSubmitFlag_NoDepth + Desc.Flags |= IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH; +#endif + + UpdatePassthroughPokeActors_GameThread(); +} + +static UWorld* GetWorld() +{ + UWorld* World = nullptr; + for (const FWorldContext& Context : GEngine->GetWorldContexts()) + { + if (Context.WorldType == EWorldType::Game || Context.WorldType == EWorldType::PIE) + { + World = Context.World(); + } + } + return World; +} + +bool FLayer::NeedsPassthroughPokeAHole() +{ + return ((Desc.Flags & IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH) && Desc.HasShape<FUserDefinedLayer>()); +} + +bool FLayer::NeedsPokeAHole() +{ +#if PLATFORM_ANDROID + return (Desc.Flags & IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH) != 0; +#else + return false; +#endif +} + +void FLayer::HandlePokeAHoleComponent() +{ + if (NeedsPokeAHole()) + { + const FString BaseComponentName = FString::Printf(TEXT("OculusPokeAHole_%d"), Id); + const FName ComponentName(*BaseComponentName); + + if (!PokeAHoleComponentPtr) { + UWorld* World = GetWorld(); + + if (!World) + { + return; + } + + PokeAHoleActor = World->SpawnActor<AActor>(); + + PokeAHoleComponentPtr = NewObject<UProceduralMeshComponent>(PokeAHoleActor, ComponentName); + PokeAHoleComponentPtr->RegisterComponent(); + + TArray<FVector> Vertices; + TArray<int32> Triangles; + TArray<FVector> Normals; + TArray<FVector2D> UV0; + TArray<FLinearColor> VertexColors; + TArray<FProcMeshTangent> Tangents; + + BuildPokeAHoleMesh(Vertices, Triangles, UV0); + PokeAHoleComponentPtr->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, false); + + FOculusXRHMD* OculusXRHMD = static_cast<FOculusXRHMD*>(GEngine->XRSystem->GetHMDDevice()); + UMaterial* PokeAHoleMaterial = OculusXRHMD->GetResourceHolder()->PokeAHoleMaterial; + UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(PokeAHoleMaterial, NULL); + PokeAHoleComponentPtr->SetMaterial(0, DynamicMaterial); + } + PokeAHoleComponentPtr->SetWorldTransform(Desc.Transform); + + } + + return; +} + +static void AppendFaceIndices(const int v0, const int v1, const int v2, const int v3, TArray<int32>& Triangles, bool inverse) +{ + if (inverse) + { + Triangles.Add(v0); + Triangles.Add(v2); + Triangles.Add(v1); + Triangles.Add(v0); + Triangles.Add(v3); + Triangles.Add(v2); + } + else + { + Triangles.Add(v0); + Triangles.Add(v1); + Triangles.Add(v2); + Triangles.Add(v0); + Triangles.Add(v2); + Triangles.Add(v3); + } +} + +void FLayer::BuildPokeAHoleMesh(TArray<FVector>& Vertices, TArray<int32>& Triangles, TArray<FVector2D>& UV0) +{ + if (Desc.HasShape<FQuadLayer>()) + { + const float QuadScale = 0.99; + + FIntPoint TexSize = Desc.Texture.IsValid() ? Desc.Texture->GetTexture2D()->GetSizeXY() : Desc.LayerSize; + float AspectRatio = TexSize.X ? (float)TexSize.Y / (float)TexSize.X : 3.0f / 4.0f; + + float QuadSizeX = Desc.QuadSize.X; + float QuadSizeY = (Desc.Flags & IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO) ? Desc.QuadSize.X * AspectRatio : Desc.QuadSize.Y; + + Vertices.Init(FVector::ZeroVector, 4); + Vertices[0] = FVector(0.0, -QuadSizeX / 2, -QuadSizeY / 2) * QuadScale; + Vertices[1] = FVector(0.0, QuadSizeX / 2, -QuadSizeY / 2) * QuadScale; + Vertices[2] = FVector(0.0, QuadSizeX / 2, QuadSizeY / 2) * QuadScale; + Vertices[3] = FVector(0.0, -QuadSizeX / 2, QuadSizeY / 2) * QuadScale; + + UV0.Init(FVector2D::ZeroVector, 4); + UV0[0] = FVector2D(1, 0); + UV0[1] = FVector2D(1, 1); + UV0[2] = FVector2D(0, 0); + UV0[3] = FVector2D(0, 1); + + Triangles.Reserve(6); + AppendFaceIndices(0, 1, 2, 3, Triangles, false); + } + else if (Desc.HasShape<FCylinderLayer>()) + { + const FCylinderLayer& CylinderProps = Desc.GetShape<FCylinderLayer>(); + const float CylinderScale = 0.99; + + FIntPoint TexSize = Desc.Texture.IsValid() ? Desc.Texture->GetTexture2D()->GetSizeXY() : Desc.LayerSize; + float AspectRatio = TexSize.X ? (float)TexSize.Y / (float)TexSize.X : 3.0f / 4.0f; + + float CylinderHeight = (Desc.Flags & IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO) ? CylinderProps.OverlayArc * AspectRatio : CylinderProps.Height; + + const FVector XAxis = FVector(1, 0, 0); + const FVector YAxis = FVector(0, 1, 0); + const FVector HalfHeight = FVector(0, 0, CylinderHeight / 2); + + const float ArcAngle = CylinderProps.OverlayArc / CylinderProps.Radius; + const int Sides = (int)( (ArcAngle * 180) / (PI * 5) ); // one triangle every 10 degrees of cylinder for a good-cheap approximation + Vertices.Init(FVector::ZeroVector, 2 * (Sides + 1)); + UV0.Init(FVector2D::ZeroVector, 2 * (Sides + 1)); + Triangles.Init(0, Sides * 6); + + float CurrentAngle = -ArcAngle / 2; + const float AngleStep = ArcAngle / Sides; + + + for (int Side = 0; Side < Sides + 1; Side++) + { + FVector MidVertex = CylinderProps.Radius * (FMath::Cos(CurrentAngle) * XAxis + FMath::Sin(CurrentAngle) * YAxis); + Vertices[2 * Side] = (MidVertex - HalfHeight) * CylinderScale; + Vertices[(2 * Side) + 1] = (MidVertex + HalfHeight) * CylinderScale; + + UV0[2 * Side] = FVector2D(1 - (Side / (float)Sides ), 0); + UV0[(2 * Side) + 1] = FVector2D(1 - (Side / (float)Sides ), 1); + + CurrentAngle += AngleStep; + + if (Side < Sides) + { + Triangles[6 * Side + 0] = 2 * Side; + Triangles[6 * Side + 2] = 2 * Side + 1; + Triangles[6 * Side + 1] = 2 * (Side + 1) + 1; + Triangles[6 * Side + 3] = 2 * Side; + Triangles[6 * Side + 5] = 2 * (Side + 1) + 1; + Triangles[6 * Side + 4] = 2 * (Side + 1); + } + } + } + else if (Desc.HasShape<FCubemapLayer>()) + { + const float CubemapScale = 1000; + Vertices.Init(FVector::ZeroVector, 8); + Vertices[0] = FVector(-1.0, -1.0, -1.0) * CubemapScale; + Vertices[1] = FVector(-1.0, -1.0, 1.0) * CubemapScale; + Vertices[2] = FVector(-1.0, 1.0, -1.0) * CubemapScale; + Vertices[3] = FVector(-1.0, 1.0, 1.0) * CubemapScale; + Vertices[4] = FVector(1.0, -1.0, -1.0) * CubemapScale; + Vertices[5] = FVector(1.0, -1.0, 1.0) * CubemapScale; + Vertices[6] = FVector(1.0, 1.0, -1.0) * CubemapScale; + Vertices[7] = FVector(1.0, 1.0, 1.0) * CubemapScale; + + Triangles.Reserve(24); + AppendFaceIndices(0, 1, 3, 2, Triangles, false); + AppendFaceIndices(4, 5, 7, 6, Triangles, true); + AppendFaceIndices(0, 1, 5, 4, Triangles, true); + AppendFaceIndices(2, 3, 7, 6, Triangles, false); + AppendFaceIndices(0, 2, 6, 4, Triangles, false); + AppendFaceIndices(1, 3, 7, 5, Triangles, true); + } +} + +bool FLayer::BuildPassthroughPokeActor(FOculusPassthroughMeshRef PassthroughMesh, FPassthroughPokeActor& OutPassthroughPokeActor) +{ + UWorld* World = GetWorld(); + + if (!World) + { + return false; + } + + const FString BaseComponentName = FString::Printf(TEXT("OculusPassthroughPoke_%d"), Id); + const FName ComponentName(*BaseComponentName); + AActor* PassthoughPokeActor = World->SpawnActor<AActor>(); + UProceduralMeshComponent* PassthoughPokeComponentPtr = NewObject<UProceduralMeshComponent>(PassthoughPokeActor, ComponentName); + PassthoughPokeComponentPtr->RegisterComponent(); + + const TArray<int32>& Triangles = PassthroughMesh->GetTriangles(); + const TArray<FVector>& Vertices = PassthroughMesh->GetVertices(); + TArray<FVector> Normals; + TArray<FVector2D> UV0; + TArray<FLinearColor> VertexColors; + TArray<FProcMeshTangent> Tangents; + + PassthoughPokeComponentPtr->CreateMeshSection_LinearColor(0, Vertices, Triangles, Normals, UV0, VertexColors, Tangents, false); + + FOculusXRHMD* OculusXRHMD = static_cast<FOculusXRHMD*>(GEngine->XRSystem->GetHMDDevice()); + UMaterial* PokeAHoleMaterial = OculusXRHMD->GetResourceHolder()->PokeAHoleMaterial; + + UMaterialInstanceDynamic* DynamicMaterial = UMaterialInstanceDynamic::Create(PokeAHoleMaterial, NULL); + PassthoughPokeComponentPtr->SetMaterial(0, DynamicMaterial); + + OutPassthroughPokeActor.PokeAHoleActor = PassthoughPokeActor; + OutPassthroughPokeActor.PokeAHoleComponentPtr = PassthoughPokeComponentPtr; + + return true; +} + +void FLayer::UpdatePassthroughPokeActors_GameThread() +{ + if (Desc.HasShape<FUserDefinedLayer>()) + { + const FUserDefinedLayer& UserDefinedLayerProps = Desc.GetShape<FUserDefinedLayer>(); + const TArray<FUserDefinedGeometryDesc>& UserGeometryList = UserDefinedLayerProps.UserGeometryList; + TSet<FString> UsedSet; + + if(NeedsPassthroughPokeAHole()) + { + for (const FUserDefinedGeometryDesc& GeometryDesc : UserGeometryList ) + { + const FString MeshName = GeometryDesc.MeshName; + UsedSet.Add(MeshName); + + FPassthroughPokeActor* FoundPassthroughPokeActor = PassthroughPokeActorMap->Find(MeshName); + if(!FoundPassthroughPokeActor) + { + OculusXRHMD::FOculusPassthroughMeshRef GeomPassthroughMesh = GeometryDesc.PassthroughMesh; + if (GeomPassthroughMesh) + { + FPassthroughPokeActor PassthroughPokeActor; + if (BuildPassthroughPokeActor(GeomPassthroughMesh, PassthroughPokeActor)) + { + PassthroughPokeActor.PokeAHoleComponentPtr->SetWorldTransform(GeometryDesc.Transform); + PassthroughPokeActorMap->Add(MeshName, PassthroughPokeActor); + } + } + } + else if (GeometryDesc.bUpdateTransform) + { + (*FoundPassthroughPokeActor).PokeAHoleComponentPtr->SetWorldTransform(GeometryDesc.Transform); + } + } + } + + // find actors that no longer exist + TArray<FString> ItemsToRemove; + for (auto &Entry : *UserDefinedGeometryMap) + { + if (!UsedSet.Contains(Entry.Key)) + { + ItemsToRemove.Add(Entry.Key); + } + } + + for (FString Entry: ItemsToRemove) + { + FPassthroughPokeActor* PassthroughPokeActor = PassthroughPokeActorMap->Find(Entry); + if (PassthroughPokeActor) + { + UWorld* World = GetWorld(); + if(World) + { + World->DestroyActor(PassthroughPokeActor->PokeAHoleActor); + } + } + PassthroughPokeActorMap->Remove(Entry); + } + } +} + +bool FLayer::ShapeNeedsTextures(ovrpShape shape) +{ + return ((shape != ovrpShape_ReconstructionPassthrough) && + (shape != ovrpShape_SurfaceProjectedPassthrough)); +} + +void FLayer::SetEyeLayerDesc(const ovrpLayerDesc_EyeFov& InEyeLayerDesc, const ovrpRecti InViewportRect[ovrpEye_Count]) +{ + OvrpLayerDesc.EyeFov = InEyeLayerDesc; + + for(int eye = 0; eye < ovrpEye_Count; eye++) + { + OvrpLayerSubmit.ViewportRect[eye] = InViewportRect[eye]; + } + + bHasDepth = InEyeLayerDesc.DepthFormat != ovrpTextureFormat_None; +} + + +TSharedPtr<FLayer, ESPMode::ThreadSafe> FLayer::Clone() const +{ + return MakeShareable(new FLayer(*this)); +} + + +bool FLayer::CanReuseResources(const FLayer* InLayer) const +{ + if (!InLayer || !InLayer->OvrpLayer.IsValid()) + { + return false; + } + + if (OvrpLayerDesc.Shape != InLayer->OvrpLayerDesc.Shape || + OvrpLayerDesc.Layout != InLayer->OvrpLayerDesc.Layout || + OvrpLayerDesc.TextureSize.w != InLayer->OvrpLayerDesc.TextureSize.w || + OvrpLayerDesc.TextureSize.h != InLayer->OvrpLayerDesc.TextureSize.h || + OvrpLayerDesc.MipLevels != InLayer->OvrpLayerDesc.MipLevels || + OvrpLayerDesc.SampleCount != InLayer->OvrpLayerDesc.SampleCount || + OvrpLayerDesc.Format != InLayer->OvrpLayerDesc.Format || + OvrpLayerDesc.LayerFlags != InLayer->OvrpLayerDesc.LayerFlags || + bNeedsTexSrgbCreate != InLayer->bNeedsTexSrgbCreate) + { + return false; + } + + if (OvrpLayerDesc.Shape == ovrpShape_EyeFov) + { + if (OvrpLayerDesc.EyeFov.DepthFormat != InLayer->OvrpLayerDesc.EyeFov.DepthFormat || + OvrpLayerDesc.EyeFov.MotionVectorDepthFormat != InLayer->OvrpLayerDesc.EyeFov.MotionVectorDepthFormat || + OvrpLayerDesc.EyeFov.MotionVectorFormat != InLayer->OvrpLayerDesc.EyeFov.MotionVectorFormat || + OvrpLayerDesc.EyeFov.MotionVectorTextureSize.w != InLayer->OvrpLayerDesc.EyeFov.MotionVectorTextureSize.w || + OvrpLayerDesc.EyeFov.MotionVectorTextureSize.h != InLayer->OvrpLayerDesc.EyeFov.MotionVectorTextureSize.h + ) + { + return false; + } + } + + return true; +} + +bool FLayer::Initialize_RenderThread(const FSettings* Settings, FCustomPresent* CustomPresent, FDeferredDeletionQueue* DeferredDeletion, FRHICommandListImmediate& RHICmdList, const FLayer* InLayer) +{ + CheckInRenderThread(); + + if (Id == 0) + { + // OvrpLayerDesc and OvrpViewportRects already initialized, as this is the eyeFOV layer. The only necessary modification is to take into account MSAA level, that can only be accurately determined on the RT. + } + else + { + bInvertY = (CustomPresent->GetLayerFlags() & ovrpLayerFlag_TextureOriginAtBottomLeft) != 0; + + uint32 SizeX = 0, SizeY = 0; + + if (Desc.Texture.IsValid()) + { + FRHITexture2D* Texture2D = Desc.Texture->GetTexture2D(); + FRHITextureCube* TextureCube = Desc.Texture->GetTextureCube(); + + if (Texture2D) + { + SizeX = Texture2D->GetSizeX(); + SizeY = Texture2D->GetSizeY(); + } + else if (TextureCube) + { + SizeX = SizeY = TextureCube->GetSize(); + } + } + else + { + SizeX = Desc.LayerSize.X; + SizeY = Desc.LayerSize.Y; + } + + ovrpShape Shape; + + if (Desc.HasShape<FQuadLayer>()) + { + Shape = ovrpShape_Quad; + } + else if (Desc.HasShape<FCylinderLayer>()) + { + Shape = ovrpShape_Cylinder; + } + else if (Desc.HasShape<FCubemapLayer>()) + { + Shape = ovrpShape_Cubemap; + } + else if (Desc.HasShape<FEquirectLayer>()) + { + Shape = ovrpShape_Equirect; + } + else if (Desc.HasShape<FReconstructedLayer>()) + { + Shape = ovrpShape_ReconstructionPassthrough; + } + else if (Desc.HasShape<FUserDefinedLayer>()) + { + Shape = ovrpShape_SurfaceProjectedPassthrough; + } + else + { + return false; + } + + if (ShapeNeedsTextures(Shape) && (SizeX == 0 || SizeY == 0)) + { + return false; + } + + + + EPixelFormat Format = Desc.Texture.IsValid() ? CustomPresent->GetPixelFormat(Desc.Texture->GetFormat()) : CustomPresent->GetDefaultPixelFormat(); +#if PLATFORM_ANDROID + uint32 NumMips = Desc.Texture.IsValid() ? Desc.Texture->GetNumMips() : 1; +#else + uint32 NumMips = 0; +#endif + uint32 NumSamples = 1; + int LayerFlags = CustomPresent->GetLayerFlags(); + + if (!(Desc.Flags & IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE)) + { + LayerFlags |= ovrpLayerFlag_Static; + } +#ifdef WITH_OCULUS_BRANCH + if (Desc.Flags & IStereoLayers::LAYER_FLAG_BICUBIC_FILTERING) + { + LayerFlags |= ovrpLayerFlag_BicubicFiltering; + } +#endif + // Calculate layer desc + FOculusXRHMDModule::GetPluginWrapper().CalculateLayerDesc( + Shape, + !Desc.LeftTexture.IsValid() ? ovrpLayout_Mono : ovrpLayout_Stereo, + ovrpSizei { (int) SizeX, (int) SizeY }, + NumMips, + NumSamples, + CustomPresent->GetOvrpTextureFormat(Format), + LayerFlags, + &OvrpLayerDesc); + + // Calculate viewport rect + for (uint32 EyeIndex = 0; EyeIndex < ovrpEye_Count; EyeIndex++) + { + ovrpRecti& ViewportRect = OvrpLayerSubmit.ViewportRect[EyeIndex]; + ViewportRect.Pos.x = (int)(Desc.UVRect.Min.X * SizeX + 0.5f); + ViewportRect.Pos.y = (int)(Desc.UVRect.Min.Y * SizeY + 0.5f); + ViewportRect.Size.w = (int)(Desc.UVRect.Max.X * SizeX + 0.5f) - ViewportRect.Pos.x; + ViewportRect.Size.h = (int)(Desc.UVRect.Max.Y * SizeY + 0.5f) - ViewportRect.Pos.y; + } + } + + // Reuse/Create texture set + if (CanReuseResources(InLayer)) + { + OvrpLayerId = InLayer->OvrpLayerId; + OvrpLayer = InLayer->OvrpLayer; + SwapChain = InLayer->SwapChain; + DepthSwapChain = InLayer->DepthSwapChain; + FoveationSwapChain = InLayer->FoveationSwapChain; + RightSwapChain = InLayer->RightSwapChain; + RightDepthSwapChain = InLayer->RightDepthSwapChain; + MotionVectorSwapChain = InLayer->MotionVectorSwapChain; + MotionVectorDepthSwapChain = InLayer->MotionVectorDepthSwapChain; + InvAlphaTexture = InLayer->InvAlphaTexture; + bUpdateTexture = InLayer->bUpdateTexture; + bNeedsTexSrgbCreate = InLayer->bNeedsTexSrgbCreate; + UserDefinedGeometryMap = InLayer->UserDefinedGeometryMap; + } + else + { + bool bLayerCreated = false; +#ifdef WITH_OCULUS_BRANCH + bool bValidFoveationTextures = true; +#else + // UE 5.1 crashes creating renderpass when using the FragmentDensityMap Extension + // This is fixed in 5.1.1 + bool bValidFoveationTextures = false; +#endif + TArray<ovrpTextureHandle> ColorTextures; + TArray<ovrpTextureHandle> DepthTextures; + TArray<ovrpTextureHandle> FoveationTextures; + TArray<ovrpTextureHandle> RightColorTextures; + TArray<ovrpTextureHandle> RightDepthTextures; + ovrpSizei FoveationTextureSize; + + bool bValidMotionVectorTextures = false; + TArray<ovrpTextureHandle> MotionVectorTextures; + ovrpSizei MotionVectorTextureSize; + TArray<ovrpTextureHandle> MotionVectorDepthTextures; + ovrpSizei MotionVectorDepthTextureSize; + + ExecuteOnRHIThread([&]() + { + // UNDONE Do this in RenderThread once OVRPlugin allows FOculusXRHMDModule::GetPluginWrapper().SetupLayer to be called asynchronously + int32 TextureCount; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().SetupLayer(CustomPresent->GetOvrpDevice(), OvrpLayerDesc.Base, (int*) &OvrpLayerId)) && + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetLayerTextureStageCount(OvrpLayerId, &TextureCount))) + { + if(ShapeNeedsTextures(OvrpLayerDesc.Shape)) + { + // Left + { + ColorTextures.SetNum(TextureCount); + if (bHasDepth) + { + DepthTextures.SetNum(TextureCount); + } + + FoveationTextures.SetNum(TextureCount); + FoveationTextureSize.w = 0; + FoveationTextureSize.h = 0; + + MotionVectorTextures.SetNum(TextureCount); + MotionVectorTextureSize.w = 0; + MotionVectorTextureSize.h = 0; + MotionVectorDepthTextures.SetNum(TextureCount); + MotionVectorDepthTextureSize.w = 0; + MotionVectorDepthTextureSize.h = 0; + + bValidMotionVectorTextures = ((OvrpLayerDesc.LayerFlags & ovrpLayerFlag_SpaceWarpDataAllocation) > 0) && (OvrpLayerDesc.Shape == ovrpShape_EyeFov); + for (int32 TextureIndex = 0; TextureIndex < TextureCount; TextureIndex++) + { + ovrpTextureHandle* DepthTexHdlPtr = bHasDepth ? &DepthTextures[TextureIndex] : nullptr; + if (!OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetLayerTexture2(OvrpLayerId, TextureIndex, ovrpEye_Left, &ColorTextures[TextureIndex], DepthTexHdlPtr))) + { + UE_LOG(LogHMD, Error, TEXT("Failed to create Oculus layer texture. NOTE: This causes a leak of %d other texture(s), which will go unused."), TextureIndex); + // skip setting bLayerCreated and allocating any other textures + return; + } + if (bValidFoveationTextures) + { + // Call fails on unsupported platforms and returns null textures for no foveation texture + // Since this texture is not required for rendering, don't return on failure + if (!OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetLayerTextureFoveation(OvrpLayerId, TextureIndex, ovrpEye_Left, &FoveationTextures[TextureIndex], &FoveationTextureSize)) || + FoveationTextures[TextureIndex] == (unsigned long long)nullptr) + { + bValidFoveationTextures = false; + } + } + + if (bValidMotionVectorTextures) + { + // Call fails on unsupported platforms and returns null textures for no motion vector texture + // Since this texture is not required for rendering, don't return on failure + + if (!OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetLayerTextureSpaceWarp(OvrpLayerId, TextureIndex, ovrpEye_Left, &MotionVectorTextures[TextureIndex], &MotionVectorTextureSize, &MotionVectorDepthTextures[TextureIndex], &MotionVectorDepthTextureSize)) || + MotionVectorTextures[TextureIndex] == (unsigned long long)nullptr) + { + bValidMotionVectorTextures = false; + UE_LOG(LogHMD, Error, TEXT("[Mobile SpaceWarp] Space Warpovrp_GetLayerTextureMotionVector failed")); + + } + } + } + } + + // Right + if(OvrpLayerDesc.Layout == ovrpLayout_Stereo) + { + RightColorTextures.SetNum(TextureCount); + if (bHasDepth) + { + RightDepthTextures.SetNum(TextureCount); + } + + for (int32 TextureIndex = 0; TextureIndex < TextureCount; TextureIndex++) + { + ovrpTextureHandle* DepthTexHdlPtr = bHasDepth ? &RightDepthTextures[TextureIndex] : nullptr; + if (!OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetLayerTexture2(OvrpLayerId, TextureIndex, ovrpEye_Right, &RightColorTextures[TextureIndex], DepthTexHdlPtr))) + { + UE_LOG(LogHMD, Error, TEXT("Failed to create Oculus layer texture. NOTE: This causes a leak of %d other texture(s), which will go unused."), TextureCount + TextureIndex); + // skip setting bLayerCreated and allocating any other textures + return; + } + } + } + } + else + { + bValidFoveationTextures = false; + } + + bLayerCreated = true; + } + }); + + if(bLayerCreated) + { + OvrpLayer = MakeShareable<FOvrpLayer>(new FOvrpLayer(OvrpLayerId, DeferredDeletion)); + + if (ShapeNeedsTextures(OvrpLayerDesc.Shape)) + { + uint32 SizeX = OvrpLayerDesc.TextureSize.w; + uint32 SizeY = OvrpLayerDesc.TextureSize.h; + EPixelFormat ColorFormat = CustomPresent->GetPixelFormat(OvrpLayerDesc.Format); + EPixelFormat DepthFormat = PF_DepthStencil; + uint32 NumMips = OvrpLayerDesc.MipLevels; + uint32 NumSamples = OvrpLayerDesc.SampleCount; + uint32 NumSamplesTileMem = 1; + if (OvrpLayerDesc.Shape == ovrpShape_EyeFov) + { + static const auto CVarMobileMSAA = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.MobileMSAA")); + NumSamplesTileMem = (CVarMobileMSAA ? CVarMobileMSAA->GetValueOnAnyThread() : 1); + } + + ERHIResourceType ResourceType; + if (OvrpLayerDesc.Shape == ovrpShape_Cubemap || OvrpLayerDesc.Shape == ovrpShape_OffcenterCubemap) + { + ResourceType = RRT_TextureCube; + } + else if (OvrpLayerDesc.Layout == ovrpLayout_Array) + { + ResourceType = RRT_Texture2DArray; + } + else + { + ResourceType = RRT_Texture2D; + } + + const bool bNeedsSRGBFlag = bNeedsTexSrgbCreate || CustomPresent->IsSRGB(OvrpLayerDesc.Format); + + ETextureCreateFlags ColorTexCreateFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable | TexCreate_ResolveTargetable | (bNeedsSRGBFlag ? TexCreate_SRGB : TexCreate_None); + ETextureCreateFlags DepthTexCreateFlags = TexCreate_ShaderResource | TexCreate_DepthStencilTargetable | TexCreate_InputAttachmentRead; + + if (Desc.Texture.IsValid()) + { + ColorTexCreateFlags |= (Desc.Texture->GetFlags() & TexCreate_SRGB); + } + + FClearValueBinding ColorTextureBinding = FClearValueBinding(); + FClearValueBinding DepthTextureBinding = GetSceneDepthClearValue(); + + SwapChain = CustomPresent->CreateSwapChain_RenderThread(SizeX, SizeY, ColorFormat, ColorTextureBinding, NumMips, NumSamples, NumSamplesTileMem, ResourceType, ColorTextures, ColorTexCreateFlags, *FString::Printf(TEXT("Oculus Color Swapchain %d"), OvrpLayerId)); + +#if PLATFORM_WINDOWS + static const auto CVarPropagateAlpha = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.PostProcessing.PropagateAlpha")); + const EAlphaChannelMode::Type PropagateAlpha = EAlphaChannelMode::FromInt(CVarPropagateAlpha->GetValueOnRenderThread()); + if (PropagateAlpha == EAlphaChannelMode::AllowThroughTonemapper) + { + ETextureCreateFlags InvTexCreateFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable; + FRHIResourceCreateInfo Info(TEXT("InvAlphaTexture")); + InvAlphaTexture = RHICreateTexture2D(SizeX, SizeY, ColorFormat, NumMips, NumSamples, InvTexCreateFlags, Info); + } +#endif + + if (bHasDepth) + { + DepthSwapChain = CustomPresent->CreateSwapChain_RenderThread(SizeX, SizeY, DepthFormat, DepthTextureBinding, 1, NumSamples, NumSamplesTileMem, ResourceType, DepthTextures, DepthTexCreateFlags, *FString::Printf(TEXT("Oculus Depth Swapchain %d"), OvrpLayerId)); + } + if (bValidFoveationTextures) + { + FoveationSwapChain = CustomPresent->CreateSwapChain_RenderThread(FoveationTextureSize.w, FoveationTextureSize.h, PF_R8G8, FClearValueBinding::White, 1, 1, 1, ResourceType, FoveationTextures, TexCreate_Foveation, *FString::Printf(TEXT("Oculus Foveation Swapchain %d"), OvrpLayerId)); + } + else + { + FoveationSwapChain.Reset(); + } + + if (bValidMotionVectorTextures) + { + EPixelFormat MvPixelFormat = PF_FloatRGBA; + ETextureCreateFlags MVTexCreateFlags = TexCreate_ShaderResource | TexCreate_RenderTargetable; + MotionVectorSwapChain = CustomPresent->CreateSwapChain_RenderThread(MotionVectorTextureSize.w, MotionVectorTextureSize.h, MvPixelFormat, FClearValueBinding::Black, 1, 1, 1, ResourceType, MotionVectorTextures, MVTexCreateFlags, *FString::Printf(TEXT("Oculus MV Swapchain %d"), OvrpLayerId)); + if (MotionVectorDepthTextures.Num() && MotionVectorDepthTextures[0] != (unsigned long long)nullptr) + { + ETextureCreateFlags MVDepthTexCreateFlags = TexCreate_ShaderResource | TexCreate_DepthStencilTargetable; + MotionVectorDepthSwapChain = CustomPresent->CreateSwapChain_RenderThread(MotionVectorDepthTextureSize.w, MotionVectorDepthTextureSize.h, PF_DepthStencil, FClearValueBinding::Black, 1, 1, 1, ResourceType, MotionVectorDepthTextures, MVDepthTexCreateFlags, *FString::Printf(TEXT("Oculus MV Depth Swapchain %d"), OvrpLayerId)); + } + else + { + MotionVectorDepthSwapChain = NULL; + } + } + else + { + MotionVectorSwapChain.Reset(); + MotionVectorDepthSwapChain.Reset(); + } + + if (OvrpLayerDesc.Layout == ovrpLayout_Stereo) + { + RightSwapChain = CustomPresent->CreateSwapChain_RenderThread(SizeX, SizeY, ColorFormat, ColorTextureBinding, NumMips, NumSamples, NumSamplesTileMem, ResourceType, RightColorTextures, ColorTexCreateFlags, *FString::Printf(TEXT("Oculus Right Color Swapchain %d"), OvrpLayerId)); + + if (bHasDepth) + { + RightDepthSwapChain = CustomPresent->CreateSwapChain_RenderThread(SizeX, SizeY, DepthFormat, DepthTextureBinding, 1, NumSamples, NumSamplesTileMem, ResourceType, RightDepthTextures, DepthTexCreateFlags, *FString::Printf(TEXT("Oculus Right Depth Swapchain %d"), OvrpLayerId)); + } + } + + bUpdateTexture = true; + } + else + { + SwapChain.Reset(); + DepthSwapChain.Reset(); + FoveationSwapChain.Reset(); + RightSwapChain.Reset(); + RightDepthSwapChain.Reset(); + if (UserDefinedGeometryMap) + { + UserDefinedGeometryMap->Reset(); + } + } + } + else + { + return false; + } + + } + + if ((Desc.Flags & IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE) && Desc.Texture.IsValid() && IsVisible()) + { + bUpdateTexture = true; + } + + return true; +} + +void FLayer::UpdatePassthroughStyle_RenderThread(const FEdgeStyleParameters& EdgeStyleParameters) +{ + ovrpInsightPassthroughStyle Style; + Style.EdgeColor = ovrpColorf { 0 , 0 , 0 , 0 }; + Style.TextureOpacityFactor = EdgeStyleParameters.TextureOpacityFactor; + Style.Flags = (ovrpInsightPassthroughStyleFlags)(ovrpInsightPassthroughStyleFlags_HasTextureOpacityFactor + | ovrpInsightPassthroughStyleFlags_HasEdgeColor + | ovrpInsightPassthroughStyleFlags_HasTextureColorMap); + Style.TextureColorMapType = ovrpInsightPassthroughColorMapType_None; + Style.TextureColorMapData = nullptr; + Style.TextureColorMapDataSize = 0; + + if(EdgeStyleParameters.bEnableEdgeColor) + { + Style.EdgeColor = ToOvrpColorf(EdgeStyleParameters.EdgeColor); + } + + if(EdgeStyleParameters.bEnableColorMap) + { + Style.TextureColorMapType = ToOVRPColorMapType(EdgeStyleParameters.ColorMapType); + Style.TextureColorMapData = (uint8*)EdgeStyleParameters.ColorMapData.GetData(); + Style.TextureColorMapDataSize = EdgeStyleParameters.ColorMapData.Num(); + } + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().SetInsightPassthroughStyle(OvrpLayerId,Style))) + { + UE_LOG(LogTemp, Error, TEXT("Failed setting passthrough style")); + return; + } +} + +static FMatrix TransformToPassthroughSpace(FTransform Transform, const FGameFrame* Frame) +{ + const FVector WorldToMetersScaleInv = FVector(Frame->WorldToMetersScale).Reciprocal(); + FTransform TransformWorld = Transform * Frame->TrackingToWorld.Inverse(); + TransformWorld.MultiplyScale3D(WorldToMetersScaleInv); + TransformWorld.ScaleTranslation(WorldToMetersScaleInv); + const FMatrix TransformWorldScaled = TransformWorld.ToMatrixWithScale(); + + const FMatrix SwapAxisMatrix( + FPlane(0.0f, 0.0f,-1.0f, 0.0f), + FPlane(1.0f, 0.0f, 0.0f, 0.0f), + FPlane(0.0f, 1.0f, 0.0f, 0.0f), + FPlane(0.0f, 0.0f, 0.0f, 1.0f) + ); + + return TransformWorldScaled * SwapAxisMatrix; +} + +void FLayer::UpdatePassthrough_RenderThread(FCustomPresent* CustomPresent, FRHICommandListImmediate& RHICmdList,const FGameFrame* Frame) +{ + CheckInRenderThread(); + if (Desc.HasShape<FReconstructedLayer>()) + { + const FReconstructedLayer& ReconstructedLayerProps = Desc.GetShape<FReconstructedLayer>(); + UpdatePassthroughStyle_RenderThread(ReconstructedLayerProps.EdgeStyleParameters); + } + else if (Desc.HasShape<FUserDefinedLayer>()) + { + const FUserDefinedLayer& UserDefinedLayerProps = Desc.GetShape<FUserDefinedLayer>(); + UpdatePassthroughStyle_RenderThread(UserDefinedLayerProps.EdgeStyleParameters); + } + + if (Desc.HasShape<FUserDefinedLayer>()) + { + const FUserDefinedLayer& UserDefinedLayerProps = Desc.GetShape<FUserDefinedLayer>(); + const TArray<FUserDefinedGeometryDesc>& UserGeometryList = UserDefinedLayerProps.UserGeometryList; + TSet<FString> UsedSet; + + for (const FUserDefinedGeometryDesc& GeometryDesc : UserGeometryList ) + { + const FString MeshName = GeometryDesc.MeshName; + UsedSet.Add(MeshName); + + FPassthroughMesh* LayerPassthroughMesh = UserDefinedGeometryMap->Find(MeshName); + if(!LayerPassthroughMesh) + { + OculusXRHMD::FOculusPassthroughMeshRef GeomPassthroughMesh= GeometryDesc.PassthroughMesh; + if (GeomPassthroughMesh) + { + const FMatrix Transform = TransformToPassthroughSpace(GeometryDesc.Transform, Frame); + uint64_t MeshHandle = 0; + uint64_t InstanceHandle = 0; + AddPassthroughMesh_RenderThread(GeomPassthroughMesh->GetVertices(), GeomPassthroughMesh->GetTriangles(), Transform, MeshHandle, InstanceHandle); + UserDefinedGeometryMap->Add(MeshName, FPassthroughMesh(MeshHandle, InstanceHandle)); + } + } + else if (GeometryDesc.bUpdateTransform) + { + const FMatrix Transform = TransformToPassthroughSpace(GeometryDesc.Transform, Frame); + UpdatePassthroughMeshTransform_RenderThread(LayerPassthroughMesh->InstanceHandle, Transform); + } + } + + // find meshes that no longer exist + TArray<FString> ItemsToRemove; + for (auto &Entry : *UserDefinedGeometryMap) + { + if (!UsedSet.Contains(Entry.Key)) + { + ItemsToRemove.Add(Entry.Key); + } + } + + for (FString Entry: ItemsToRemove) + { + FPassthroughMesh* PassthroughMesh = UserDefinedGeometryMap->Find(Entry); + if (PassthroughMesh) + { + const uint64_t MeshHandle = PassthroughMesh->MeshHandle; + const uint64_t InstanceHandle = PassthroughMesh->InstanceHandle; + RemovePassthroughMesh_RenderThread(MeshHandle,InstanceHandle); + } + else + { + UE_LOG(LogTemp, Error, TEXT("PassthroughMesh: %s doesn't exist."), *Entry); + return; + } + + UserDefinedGeometryMap->Remove(Entry); + } + } +} + +static void InvertTextureAlpha_RenderThread(FCustomPresent* CustomPresent, FRHICommandListImmediate& RHICmdList, FRHITexture* Texture, FRHITexture* TempTexture, const ovrpRecti& ViewportRect) +{ + { + FRHITexture* SrcTexture = Texture; + FRHITexture* DstTexture = TempTexture; + const FIntRect SrcRect(ViewportRect.Pos.x, ViewportRect.Pos.y, ViewportRect.Pos.x + ViewportRect.Size.w, ViewportRect.Pos.y + ViewportRect.Size.h); + const FIntRect DstRect(0, 0, ViewportRect.Size.w, ViewportRect.Size.h); + + const bool bAlphaPremultiply = false; + const bool bNoAlphaWrite = false; + const bool bInvertSrcY = false; + const bool sRGBSource = false; + const bool bInvertAlpha = true; + + CustomPresent->CopyTexture_RenderThread(RHICmdList, DstTexture, SrcTexture, DstRect, SrcRect, bAlphaPremultiply, bNoAlphaWrite, bInvertSrcY, sRGBSource, bInvertAlpha); + } + + { + FRHICopyTextureInfo CopyInfo; + CopyInfo.Size = FIntVector(ViewportRect.Size.w, ViewportRect.Size.h, 1); + CopyInfo.SourcePosition = FIntVector::ZeroValue; + CopyInfo.DestPosition = FIntVector(ViewportRect.Pos.x, ViewportRect.Pos.y, 0); + + FRHITexture* SrcTexture = TempTexture; + FRHITexture* DstTexture = Texture; + RHICmdList.Transition(FRHITransitionInfo(SrcTexture, ERHIAccess::Unknown, ERHIAccess::CopySrc)); + RHICmdList.Transition(FRHITransitionInfo(DstTexture, ERHIAccess::Unknown, ERHIAccess::CopyDest)); + RHICmdList.CopyTexture(SrcTexture, DstTexture, CopyInfo); + RHICmdList.Transition(FRHITransitionInfo(DstTexture, ERHIAccess::CopyDest, ERHIAccess::SRVMask)); + RHICmdList.Transition(FRHITransitionInfo(SrcTexture, ERHIAccess::CopySrc, ERHIAccess::SRVMask)); + } +} + +void FLayer::UpdateTexture_RenderThread(FCustomPresent* CustomPresent, FRHICommandListImmediate& RHICmdList) +{ + CheckInRenderThread(); + + if (bUpdateTexture && SwapChain.IsValid()) + { + // Copy textures + if (Desc.Texture.IsValid()) + { + bool bAlphaPremultiply = true; + bool bNoAlphaWrite = (Desc.Flags & IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL) != 0; + + // Left + { + FRHITexture* SrcTexture = Desc.LeftTexture.IsValid() ? Desc.LeftTexture : Desc.Texture; + FRHITexture* DstTexture = SwapChain->GetTexture(); + + const ovrpRecti& OvrpViewportRect = OvrpLayerSubmit.ViewportRect[ovrpEye_Left]; + FIntRect DstRect(OvrpViewportRect.Pos.x, OvrpViewportRect.Pos.y, OvrpViewportRect.Pos.x + OvrpViewportRect.Size.w, OvrpViewportRect.Pos.y + OvrpViewportRect.Size.h); + + CustomPresent->CopyTexture_RenderThread(RHICmdList, DstTexture, SrcTexture, DstRect, FIntRect(), bAlphaPremultiply, bNoAlphaWrite, bInvertY); + } + + // Right + if(OvrpLayerDesc.Layout != ovrpLayout_Mono) + { + FRHITexture* SrcTexture = Desc.Texture; + FRHITexture* DstTexture = RightSwapChain.IsValid() ? RightSwapChain->GetTexture() : SwapChain->GetTexture(); + + const ovrpRecti& OvrpViewportRect = OvrpLayerSubmit.ViewportRect[ovrpEye_Right]; + FIntRect DstRect(OvrpViewportRect.Pos.x, OvrpViewportRect.Pos.y, OvrpViewportRect.Pos.x + OvrpViewportRect.Size.w, OvrpViewportRect.Pos.y + OvrpViewportRect.Size.h); + + CustomPresent->CopyTexture_RenderThread(RHICmdList, DstTexture, SrcTexture, DstRect, FIntRect(), bAlphaPremultiply, bNoAlphaWrite, bInvertY); + } + + bUpdateTexture = false; + } + + // Generate mips + SwapChain->GenerateMips_RenderThread(RHICmdList); + + if (RightSwapChain.IsValid()) + { + RightSwapChain->GenerateMips_RenderThread(RHICmdList); + } + } + + if (Id == 0 && SwapChain.IsValid() && InvAlphaTexture) + { + // Left + { + const ovrpRecti& OvrpViewportRect = OvrpLayerSubmit.ViewportRect[ovrpEye_Left]; + FRHITexture* EyeTexture = SwapChain->GetTexture(); + InvertTextureAlpha_RenderThread(CustomPresent, RHICmdList, EyeTexture, InvAlphaTexture, OvrpViewportRect); + } + + // Right + if(OvrpLayerDesc.Layout != ovrpLayout_Mono) + { + const ovrpRecti& OvrpViewportRect = OvrpLayerSubmit.ViewportRect[ovrpEye_Right]; + FRHITexture* EyeTexture = RightSwapChain.IsValid() ? RightSwapChain->GetTexture() : SwapChain->GetTexture(); + InvertTextureAlpha_RenderThread(CustomPresent, RHICmdList, EyeTexture, InvAlphaTexture, OvrpViewportRect); + } + } + +} + +// Returned how much the tracking space moved from previous frame to current frame. +// Note: FTransform is following the order of C = A * B, Apply C means, apply A then Apply B. +void GetTrackingSpaceDeltaPose(const FSettings* Settings, const FGameFrame* Frame, FTransform& TrackingSpaceDeltaPose) +{ + // TrackingSpaceDeltaPose: describe the tracking space movement in current tracking space + TrackingSpaceDeltaPose = Frame->TrackingToWorld * Frame->LastTrackingToWorld.Inverse(); + + // However There is a intermediete layer from SettingBasePose, which is acting as a bridge between OVRPlugin Device Space and UE4 Device Space + // Define: OVRPlugin-Device-FPose: same space with the OvrPlugin returned Pose ( In FPose, not ovrpPose ). FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3() returned value in this space + // Define: UE4-Device-FPose: the space UE4 seeing as device pose space. GetCurrentPose() returned result in this space + FTransform SettingBasePose = FTransform(Settings->BaseOrientation, Settings->BaseOffset); + + // According context of GetCurrentPose, all OVRPlugin-Device-FPose will be inversed by Settings->BaseOrientation & Settings->BaseOffset + // before exposing to UE4 as UE4-Device-FPose. which means UE4-Device-FPose = OVRPlugin-Device-FPose * FPose(BaseOrientation, BaseOffset ).Inverse() + // FPose(BaseOrientation, BaseOffset ) is the UE4 device reference frame defined in OVRPlugin Device Space ( In FPose, not ovrpPose ). + // OVRPlugin-Device-FPose_To_UE4-Device-FPose = FPose(BaseOrientation, BaseOffset ).Inverse() + // UE4-Device-FPose_To_OVRPlugin-Device-FPose = FPose(BaseOrientation, BaseOffset ) + + // Eventually we want a transform in OvrPlugin space (both the input and output data are in OvrPlugin Device Space) + TrackingSpaceDeltaPose = SettingBasePose.Inverse() * TrackingSpaceDeltaPose * SettingBasePose; +} + +const ovrpLayerSubmit* FLayer::UpdateLayer_RHIThread(const FSettings* Settings, const FGameFrame* Frame, const int LayerIndex) +{ + OvrpLayerSubmit.LayerId = OvrpLayerId; + OvrpLayerSubmit.TextureStage = SwapChain.IsValid() ? SwapChain->GetSwapChainIndex_RHIThread() : 0; + + bool injectColorScale = Id == 0 || Settings->bApplyColorScaleAndOffsetToAllLayers; + OvrpLayerSubmit.ColorOffset = injectColorScale ? Settings->ColorOffset : ovrpVector4f{ 0, 0, 0, 0 }; + OvrpLayerSubmit.ColorScale = injectColorScale ? Settings->ColorScale : ovrpVector4f{ 1, 1, 1, 1}; + + if (OvrpLayerDesc.Shape == ovrpShape_Equirect) { + const FEquirectLayer& EquirectProps = Desc.GetShape<FEquirectLayer>(); + + ovrpTextureRectMatrixf& RectMatrix = OvrpLayerSubmit.TextureRectMatrix; + ovrpRectf& LeftUVRect = RectMatrix.LeftRect; + ovrpRectf& RightUVRect = RectMatrix.RightRect; + LeftUVRect.Pos.x = EquirectProps.LeftUVRect.Min.X; + LeftUVRect.Pos.y = EquirectProps.LeftUVRect.Min.Y; + LeftUVRect.Size.w = EquirectProps.LeftUVRect.Max.X - EquirectProps.LeftUVRect.Min.X; + LeftUVRect.Size.h = EquirectProps.LeftUVRect.Max.Y - EquirectProps.LeftUVRect.Min.Y; + RightUVRect.Pos.x = EquirectProps.RightUVRect.Min.X; + RightUVRect.Pos.y = EquirectProps.RightUVRect.Min.Y; + RightUVRect.Size.w = EquirectProps.RightUVRect.Max.X - EquirectProps.RightUVRect.Min.X; + RightUVRect.Size.h = EquirectProps.RightUVRect.Max.Y - EquirectProps.RightUVRect.Min.Y; + + ovrpVector4f& LeftScaleBias = RectMatrix.LeftScaleBias; + LeftScaleBias.x = EquirectProps.LeftScale.X; + LeftScaleBias.y = EquirectProps.LeftScale.Y; + LeftScaleBias.z = EquirectProps.LeftBias.X; + LeftScaleBias.w = EquirectProps.LeftBias.Y; + ovrpVector4f& RightScaleBias = RectMatrix.RightScaleBias; + RightScaleBias.x = EquirectProps.RightScale.X; + RightScaleBias.y = EquirectProps.RightScale.Y; + RightScaleBias.z = EquirectProps.RightBias.X; + RightScaleBias.w = EquirectProps.RightBias.Y; + + OvrpLayerSubmit.OverrideTextureRectMatrix = ovrpBool_True; + } + + if (Id != 0) + { + int SizeX = OvrpLayerDesc.TextureSize.w; + int SizeY = OvrpLayerDesc.TextureSize.h; + + float AspectRatio = SizeX ? (float)SizeY / (float)SizeX : 3.0f / 4.0f; + FVector LocationScaleInv(Frame->WorldToMetersScale); + FVector LocationScale = LocationScaleInv.Reciprocal(); + ovrpVector3f Scale = ToOvrpVector3f(Desc.Transform.GetScale3D() * LocationScale); + + switch (OvrpLayerDesc.Shape) + { + case ovrpShape_ReconstructionPassthrough: + { + float QuadSizeY = (Desc.Flags & IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO) ? Desc.QuadSize.X * AspectRatio : Desc.QuadSize.Y; + OvrpLayerSubmit.Quad.Size = ovrpSizef { static_cast<float>(Desc.QuadSize.X * Scale.x), static_cast<float>(QuadSizeY * Scale.y) }; + } + break; + + case ovrpShape_Quad: + { + float QuadSizeY = (Desc.Flags & IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO) ? Desc.QuadSize.X * AspectRatio : Desc.QuadSize.Y; + OvrpLayerSubmit.Quad.Size = ovrpSizef { static_cast<float>(Desc.QuadSize.X * Scale.x), static_cast<float>(QuadSizeY * Scale.y) }; + } + break; + case ovrpShape_Cylinder: + { + const FCylinderLayer& CylinderProps = Desc.GetShape<FCylinderLayer>(); + float CylinderHeight = (Desc.Flags & IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO) ? CylinderProps.OverlayArc * AspectRatio : CylinderProps.Height; + OvrpLayerSubmit.Cylinder.ArcWidth = CylinderProps.OverlayArc * Scale.x; + OvrpLayerSubmit.Cylinder.Height = CylinderHeight * Scale.x; + OvrpLayerSubmit.Cylinder.Radius = CylinderProps.Radius * Scale.x; + } + break; + } + + FQuat BaseOrientation; + FVector BaseLocation; + + switch (Desc.PositionType) + { + case IStereoLayers::WorldLocked: + BaseOrientation = Frame->TrackingToWorld.GetRotation(); + BaseLocation = Frame->TrackingToWorld.GetTranslation(); + break; + + case IStereoLayers::TrackerLocked: + BaseOrientation = FQuat::Identity; + BaseLocation = FVector::ZeroVector; + break; + + case IStereoLayers::FaceLocked: + BaseOrientation = FQuat::Identity; + BaseLocation = FVector::ZeroVector; + break; + } + + FTransform PlayerTransform(BaseOrientation, BaseLocation); + + FQuat Orientation = BaseOrientation.Inverse() * Desc.Transform.Rotator().Quaternion(); + FVector Location = PlayerTransform.InverseTransformPosition(Desc.Transform.GetLocation()); + FPose OutLayerPose = FPose(Orientation, Location); + if(Desc.PositionType != IStereoLayers::FaceLocked) + ConvertPose_Internal(FPose(Orientation,Location), OutLayerPose, Settings->BaseOrientation.Inverse(), Settings->BaseOrientation.Inverse().RotateVector(-Settings->BaseOffset*LocationScaleInv), 1.0); + + OvrpLayerSubmit.Pose.Orientation = ToOvrpQuatf(OutLayerPose.Orientation); + OvrpLayerSubmit.Pose.Position = ToOvrpVector3f(OutLayerPose.Position * LocationScale); + OvrpLayerSubmit.LayerSubmitFlags = 0; + + if (Desc.PositionType == IStereoLayers::FaceLocked) + { + OvrpLayerSubmit.LayerSubmitFlags |= ovrpLayerSubmitFlag_HeadLocked; + } + + if (!(Desc.Flags & IStereoLayers::LAYER_FLAG_SUPPORT_DEPTH)) + { + OvrpLayerSubmit.LayerSubmitFlags |= ovrpLayerSubmitFlag_NoDepth; + } + + } + else + { + OvrpLayerSubmit.EyeFov.DepthFar = 0; + OvrpLayerSubmit.EyeFov.DepthNear = Frame->NearClippingPlane / 100.f; //physical scale is 100UU/meter + OvrpLayerSubmit.LayerSubmitFlags = ovrpLayerSubmitFlag_ReverseZ; + + if (Settings->Flags.bPixelDensityAdaptive) + { + for (int eye = 0; eye < ovrpEye_Count; eye++) + { + OvrpLayerSubmit.ViewportRect[eye] = ToOvrpRecti(Settings->EyeRenderViewport[eye]); + } + } + + OvrpLayerSubmit.EyeFov.Fov[0] = Frame->Fov[0]; + OvrpLayerSubmit.EyeFov.Fov[1] = Frame->Fov[1]; + + static const auto CVarOculusEnableSpaceWarpInternal = IConsoleManager::Get().FindTConsoleVariableDataInt(TEXT("r.Mobile.Oculus.SpaceWarp.EnableInternal")); + if (CVarOculusEnableSpaceWarpInternal->GetValueOnAnyThread()!=0) + { + OvrpLayerSubmit.LayerSubmitFlags |= ovrpLayerSubmitFlag_SpaceWarp; + OvrpLayerSubmit.EyeFov.MotionVectorDepthFar = Frame->NearClippingPlane / 100.f; + OvrpLayerSubmit.EyeFov.MotionVectorDepthNear = INFINITY; + OvrpLayerSubmit.EyeFov.MotionVectorOffset = ovrpVector4f {0.0f, 0.0f, 0.0f, 0.0f}; + OvrpLayerSubmit.EyeFov.MotionVectorScale = ovrpVector4f{ 1.0f, 1.0f, 1.0f, 1.0f }; + + FTransform TrackingSpaceDeltaPose; + GetTrackingSpaceDeltaPose(Settings, Frame, TrackingSpaceDeltaPose); + OvrpLayerSubmit.EyeFov.AppSpaceDeltaPose.Orientation = ToOvrpQuatf(TrackingSpaceDeltaPose.GetRotation()); + OvrpLayerSubmit.EyeFov.AppSpaceDeltaPose.Position = ToOvrpVector3f(TrackingSpaceDeltaPose.GetLocation() / Frame->WorldToMetersScale); + } + + ovrpXrApi NativeXrApi; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNativeXrApiType(&NativeXrApi)) && (NativeXrApi == ovrpXrApi_OpenXR)) + { + if (LayerIndex == 0) + { + OvrpLayerSubmit.LayerSubmitFlags |= ovrpLayerSubmitFlag_IgnoreSourceAlpha; + } + else if (InvAlphaTexture == nullptr) + { + OvrpLayerSubmit.HasBlendFactors = true; + OvrpLayerSubmit.SrcBlendFactor = ovrpBlendFactorOneMinusSrcAlpha; + OvrpLayerSubmit.DstBlendFactor = ovrpBlendFactorSrcAlpha; + } + } + else + { +#if PLATFORM_WINDOWS + OvrpLayerSubmit.LayerSubmitFlags |= ovrpLayerSubmitFlag_IgnoreSourceAlpha; +#else + OvrpLayerSubmit.LayerSubmitFlags |= ovrpLayerSubmitFlag_InverseAlpha; +#endif + } + + } + + return &OvrpLayerSubmit.Base; +} + + +void FLayer::IncrementSwapChainIndex_RHIThread(FCustomPresent* CustomPresent) +{ + CheckInRHIThread(); + + if (SwapChain.IsValid()) + { + SwapChain->IncrementSwapChainIndex_RHIThread(); + } + + if (DepthSwapChain.IsValid()) + { + DepthSwapChain->IncrementSwapChainIndex_RHIThread(); + } + + if (FoveationSwapChain.IsValid()) + { + FoveationSwapChain->IncrementSwapChainIndex_RHIThread(); + } + + if (RightSwapChain.IsValid()) + { + RightSwapChain->IncrementSwapChainIndex_RHIThread(); + } + + if (RightDepthSwapChain.IsValid()) + { + RightDepthSwapChain->IncrementSwapChainIndex_RHIThread(); + } + + if (MotionVectorSwapChain.IsValid()) + { + MotionVectorSwapChain->IncrementSwapChainIndex_RHIThread(); + } + + if (MotionVectorDepthSwapChain.IsValid()) + { + MotionVectorDepthSwapChain->IncrementSwapChainIndex_RHIThread(); + } +} + + +void FLayer::ReleaseResources_RHIThread() +{ + CheckInRHIThread(); + + OvrpLayerId = 0; + OvrpLayer.Reset(); + SwapChain.Reset(); + DepthSwapChain.Reset(); + FoveationSwapChain.Reset(); + RightSwapChain.Reset(); + RightDepthSwapChain.Reset(); + MotionVectorSwapChain.Reset(); + MotionVectorDepthSwapChain.Reset(); + bUpdateTexture = false; +} + +void FLayer::AddPassthroughMesh_RenderThread(const TArray<FVector>& Vertices,const TArray<int32>& Triangles, FMatrix Transformation, uint64_t& OutMeshHandle, uint64_t& OutInstanceHandle) +{ + CheckInRenderThread(); + + uint64_t MeshHandle = 0; + uint64_t InstanceHandle = 0; + + // Explicit conversion is needed since FVector contains double elements. + // Converting Vertices.Data() to float* causes issues when memory is parsed. + TArray<float> VertexData; + VertexData.SetNumUninitialized(Vertices.Num() * 3); + + size_t i = 0; + for (const FVector& vertex : Vertices) + { + VertexData[i++] = vertex.X; + VertexData[i++] = vertex.Y; + VertexData[i++] = vertex.Z; + } + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().CreateInsightTriangleMesh( + OvrpLayerId, + VertexData.GetData(), + Vertices.Num(), + (int*)Triangles.GetData(), + Triangles.Num() / 3, + &MeshHandle))) + { + UE_LOG(LogTemp, Error, TEXT("Failed creating passthrough mesh surface.")); + return; + } + + const ovrpMatrix4f OvrTransformation = ToOvrpMatrix(Transformation); + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().AddInsightPassthroughSurfaceGeometry( + OvrpLayerId, + MeshHandle, + OvrTransformation, + &InstanceHandle))) + { + UE_LOG(LogTemp, Error, TEXT("Failed adding passthrough mesh surface to scene.")); + return; + } + OutMeshHandle = MeshHandle; + OutInstanceHandle = InstanceHandle; + +} + +void FLayer::UpdatePassthroughMeshTransform_RenderThread(uint64_t InstanceHandle,FMatrix Transformation) +{ + CheckInRenderThread(); + + const ovrpMatrix4f OvrTransformation = ToOvrpMatrix(Transformation); + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().UpdateInsightPassthroughGeometryTransform( + InstanceHandle, + OvrTransformation))) + { + UE_LOG(LogTemp, Error, TEXT("Failed updating passthrough mesh surface transform.")); + return; + } +} + +void FLayer::RemovePassthroughMesh_RenderThread(uint64_t MeshHandle, uint64_t InstanceHandle) +{ + CheckInRenderThread(); + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().DestroyInsightPassthroughGeometryInstance(InstanceHandle))) + { + UE_LOG(LogTemp, Error, TEXT("Failed removing passthrough surface from scene.")); + return; + } + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().DestroyInsightTriangleMesh(MeshHandle))) + { + UE_LOG(LogTemp, Error, TEXT("Failed destroying passthrough surface mesh.")); + return; + } +} + +void FLayer::DestroyLayer() +{ + CheckInGameThread(); + + if (PassthroughPokeActorMap) + { + UWorld* World = GetWorld(); + if (!World) + { + return; + } + + for ( auto& Entry : *PassthroughPokeActorMap) + { + World->DestroyActor(Entry.Value.PokeAHoleActor); + } + PassthroughPokeActorMap.Reset(); + } +} + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Layer.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Layer.h new file mode 100644 index 0000000000000000000000000000000000000000..18b0816db53e39ef51a339760d6a5a336fcbb955 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Layer.h @@ -0,0 +1,235 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" +#include "ProceduralMeshComponent.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD_CustomPresent.h" +#include "XRSwapChain.h" +#include "OculusXRPassthroughLayerShapes.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FOvrpLayer +//------------------------------------------------------------------------------------------------- +class FDeferredDeletionQueue; + +class FOvrpLayer : public TSharedFromThis<FOvrpLayer, ESPMode::ThreadSafe> +{ +public: + FOvrpLayer(uint32 InOvrpLayerId, FDeferredDeletionQueue* InDeferredDeletion); + ~FOvrpLayer(); + +protected: + uint32 OvrpLayerId; + +private: + FDeferredDeletionQueue* DeferredDeletion; // necessary for deferred deletion queue of the actual OvrpLayer +}; + +typedef TSharedPtr<FOvrpLayer, ESPMode::ThreadSafe> FOvrpLayerPtr; + + +//------------------------------------------------------------------------------------------------- +// FLayer +//------------------------------------------------------------------------------------------------- + +class FLayer : public TSharedFromThis<FLayer, ESPMode::ThreadSafe> +{ +public: + FLayer(uint32 InId, const IStereoLayers::FLayerDesc& InDesc); + FLayer(const FLayer& InLayer); + ~FLayer(); + + uint32 GetId() const { return Id; } + void SetDesc(const IStereoLayers::FLayerDesc& InDesc); + const IStereoLayers::FLayerDesc& GetDesc() const { return Desc; } + void SetEyeLayerDesc(const ovrpLayerDesc_EyeFov& InEyeLayerDesc, const ovrpRecti InViewportRect[ovrpEye_Count]); + const FXRSwapChainPtr& GetSwapChain() const { return SwapChain; } + const FXRSwapChainPtr& GetRightSwapChain() const { return RightSwapChain; } + const FXRSwapChainPtr& GetDepthSwapChain() const { return DepthSwapChain; } + const FXRSwapChainPtr& GetFoveationSwapChain() const { return FoveationSwapChain; } + const FXRSwapChainPtr& GetMotionVectorSwapChain() const { return MotionVectorSwapChain; } + const FXRSwapChainPtr& GetMotionVectorDepthSwapChain() const { return MotionVectorDepthSwapChain; } + void MarkTextureForUpdate() { bUpdateTexture = true; } + bool NeedsPokeAHole(); + void HandlePokeAHoleComponent(); + void BuildPokeAHoleMesh(TArray<FVector>& Vertices, TArray<int32>& Triangles, TArray<FVector2D>& UV0); + bool NeedsPassthroughPokeAHole(); + + bool ShapeNeedsTextures(ovrpShape shape); + + FTextureRHIRef GetTexture() { return Desc.Texture; } + + TSharedPtr<FLayer, ESPMode::ThreadSafe> Clone() const; + + bool CanReuseResources(const FLayer* InLayer) const; + bool Initialize_RenderThread(const FSettings* Settings, FCustomPresent* CustomPresent, FDeferredDeletionQueue* DeferredDeletion, FRHICommandListImmediate& RHICmdList, const FLayer* InLayer = nullptr); + void UpdateTexture_RenderThread(FCustomPresent* CustomPresent, FRHICommandListImmediate& RHICmdList); + void UpdatePassthrough_RenderThread(FCustomPresent* CustomPresent, FRHICommandListImmediate& RHICmdList,const FGameFrame* Frame); + + const ovrpLayerSubmit* UpdateLayer_RHIThread(const FSettings* Settings, const FGameFrame* Frame, const int LayerIndex); + void IncrementSwapChainIndex_RHIThread(FCustomPresent* CustomPresent); + void ReleaseResources_RHIThread(); + bool IsVisible() { return (Desc.Flags & IStereoLayers::LAYER_FLAG_HIDDEN) == 0; } + + bool bNeedsTexSrgbCreate; + + void AddPassthroughMesh_RenderThread(const TArray<FVector>& Vertices, const TArray<int32>& Triangles, FMatrix Transformation, uint64_t& OutMeshHandle, uint64_t& OutInstanceHandle); + void UpdatePassthroughMeshTransform_RenderThread(uint64_t InstanceHandle,FMatrix Transformation); + void RemovePassthroughMesh_RenderThread(uint64_t MeshHandle, uint64_t InstanceHandle); + + void DestroyLayer(); + +protected: + + struct FPassthroughMesh + { + FPassthroughMesh(uint64_t MeshHandle, uint64_t InstanceHandle) + : MeshHandle(MeshHandle) + , InstanceHandle(InstanceHandle) + { + } + uint64_t MeshHandle; + uint64_t InstanceHandle; + }; + + typedef TSharedPtr<TMap<FString,FPassthroughMesh>, ESPMode::ThreadSafe> FUserDefinedGeometryMapPtr; + + void UpdatePassthroughStyle_RenderThread(const FEdgeStyleParameters& EdgeStyleParameters); + + struct FPassthroughPokeActor + { + FPassthroughPokeActor() + {}; + FPassthroughPokeActor(UProceduralMeshComponent* PokeAHoleComponentPtr, AActor* PokeAHoleActor) + : PokeAHoleComponentPtr(PokeAHoleComponentPtr) + , PokeAHoleActor(PokeAHoleActor) + { + }; + UProceduralMeshComponent* PokeAHoleComponentPtr; + AActor* PokeAHoleActor; + }; + + typedef TSharedPtr<TMap<FString,FPassthroughPokeActor>, ESPMode::ThreadSafe> FPassthroughPokeActorMapPtr; + + bool BuildPassthroughPokeActor(FOculusPassthroughMeshRef PassthroughMesh, FPassthroughPokeActor& OutPassthroughPokeActor); + void UpdatePassthroughPokeActors_GameThread(); + + + uint32 Id; + IStereoLayers::FLayerDesc Desc; + int OvrpLayerId; + ovrpLayerDescUnion OvrpLayerDesc; + ovrpLayerSubmitUnion OvrpLayerSubmit; + FOvrpLayerPtr OvrpLayer; + FXRSwapChainPtr SwapChain; + FXRSwapChainPtr DepthSwapChain; + FXRSwapChainPtr FoveationSwapChain; + FXRSwapChainPtr RightSwapChain; + FXRSwapChainPtr RightDepthSwapChain; + FXRSwapChainPtr MotionVectorSwapChain; + FXRSwapChainPtr MotionVectorDepthSwapChain; + FTexture2DRHIRef InvAlphaTexture; + bool bUpdateTexture; + bool bInvertY; + bool bHasDepth; + + UProceduralMeshComponent* PokeAHoleComponentPtr; + AActor* PokeAHoleActor; + + FUserDefinedGeometryMapPtr UserDefinedGeometryMap; + FPassthroughPokeActorMapPtr PassthroughPokeActorMap; +}; + +typedef TSharedPtr<FLayer, ESPMode::ThreadSafe> FLayerPtr; + + +//------------------------------------------------------------------------------------------------- +// FLayerPtr_CompareId +//------------------------------------------------------------------------------------------------- + +struct FLayerPtr_CompareId +{ + FORCEINLINE bool operator()(const FLayerPtr& A, const FLayerPtr& B) const + { + return A->GetId() < B->GetId(); + } +}; + + +//------------------------------------------------------------------------------------------------- +// FLayerPtr_ComparePriority +//------------------------------------------------------------------------------------------------- + +struct FLayerPtr_ComparePriority +{ + FORCEINLINE bool operator()(const FLayerPtr& A, const FLayerPtr& B) const + { + if (A->GetDesc().Priority < B->GetDesc().Priority) + return true; + if (A->GetDesc().Priority > B->GetDesc().Priority) + return false; + + return A->GetId() < B->GetId(); + } +}; + +struct FLayerPtr_CompareTotal +{ + FORCEINLINE int32 GetLayerTypePriority(const FLayerPtr& A) const + { + // Draw FReconstructedLayer, PoleAHole layers (Android only), EyeFov layer, followed by other layers + const bool IsEyeFov = (A->GetId() == 0); + const bool IsPokeAHole = A->NeedsPokeAHole() || A->NeedsPassthroughPokeAHole(); + bool IsUnderlay = false; + + if(A->GetDesc().HasShape<FReconstructedLayer>()) + { + const FReconstructedLayer& ReconstructedLayerProps = A->GetDesc().GetShape<FReconstructedLayer>(); + IsUnderlay = (ReconstructedLayerProps.PassthroughLayerOrder == PassthroughLayerOrder_Underlay); + } + else if (A->GetDesc().HasShape<FUserDefinedLayer>()) + { + const FUserDefinedLayer& UserDefinedLayerProps = A->GetDesc().GetShape<FUserDefinedLayer>(); + IsUnderlay = (UserDefinedLayerProps.PassthroughLayerOrder == PassthroughLayerOrder_Underlay); + } + + const int32 Priority = IsUnderlay ? -2 : IsPokeAHole ? -1 : IsEyeFov ? 0 : 1; + return Priority; + } + + FORCEINLINE bool operator()(const FLayerPtr& A, const FLayerPtr& B) const + { + // First order layers by type + int32 PassA = GetLayerTypePriority(A); + int32 PassB = GetLayerTypePriority(B); + + if (PassA != PassB) + return PassA < PassB; + + // Draw non-FaceLocked layers first + const IStereoLayers::FLayerDesc& DescA = A->GetDesc(); + const IStereoLayers::FLayerDesc& DescB = B->GetDesc(); + + bool bFaceLockedA = (DescA.PositionType == IStereoLayers::ELayerType::FaceLocked); + bool bFaceLockedB = (DescB.PositionType == IStereoLayers::ELayerType::FaceLocked); + + if (bFaceLockedA != bFaceLockedB) + return !bFaceLockedA; + + // Draw layers by ascending priority + if (DescA.Priority != DescB.Priority) + return DescA.Priority < DescB.Priority; + + // Draw layers by ascending id + return A->GetId() < B->GetId(); + } +}; + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Settings.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Settings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d634a1314da73e9c13e95cc9699dd7bac97899d4 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Settings.cpp @@ -0,0 +1,103 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_Settings.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FSettings +//------------------------------------------------------------------------------------------------- + +FSettings::FSettings() : + BaseOffset(0, 0, 0) + , BaseOrientation(FQuat::Identity) + , PixelDensity(1.0f) + , PixelDensityMin(0.5f) + , PixelDensityMax(1.0f) + , SystemHeadset(ovrpSystemHeadset_None) + , SuggestedCpuPerfLevel(EOculusXRProcessorPerformanceLevel::SustainedLow) + , SuggestedGpuPerfLevel(EOculusXRProcessorPerformanceLevel::SustainedHigh) + , FoveatedRenderingMethod(EOculusXRFoveatedRenderingMethod::FixedFoveatedRendering) + , FoveatedRenderingLevel(EOculusXRFoveatedRenderingLevel::Off) + , bDynamicFoveatedRendering(true) + , bSupportEyeTrackedFoveatedRendering(false) + , XrApi(EOculusXRXrApi::OVRPluginOpenXR) + , ColorSpace(EOculusXRColorSpace::P3) + , ControllerPoseAlignment(EOculusXRControllerPoseAlignment::Default) + , HandTrackingSupport(EOculusXRHandTrackingSupport::ControllersOnly) + , HandTrackingFrequency(EOculusXRHandTrackingFrequency::LOW) + , ColorScale(ovrpVector4f{1,1,1,1}) + , ColorOffset(ovrpVector4f{0,0,0,0}) + , bApplyColorScaleAndOffsetToAllLayers(false) + , bLateLatching(false) + , bSupportExperimentalFeatures(false) +{ + Flags.Raw = 0; + Flags.bHMDEnabled = true; + Flags.bUpdateOnRT = true; + Flags.bHQBuffer = false; +#if PLATFORM_ANDROID + Flags.bCompositeDepth = false; + Flags.bsRGBEyeBuffer = true; + //oculus mobile is always-on stereo, no need for enableStereo codepaths + Flags.bStereoEnabled = true; + CurrentShaderPlatform = EShaderPlatform::SP_VULKAN_ES3_1_ANDROID; +#else + Flags.bCompositeDepth = true; + Flags.bsRGBEyeBuffer = false; + Flags.bStereoEnabled = false; + CurrentShaderPlatform = EShaderPlatform::SP_PCD3D_SM5; +#endif + + Flags.bSupportsDash = true; + Flags.bFocusAware = true; + Flags.bRequiresSystemKeyboard = false; + Flags.bInsightPassthroughEnabled = false; + Flags.bAnchorSupportEnabled = false; + Flags.bBodyTrackingEnabled = false; + Flags.bEyeTrackingEnabled = false; + Flags.bFaceTrackingEnabled = false; + EyeRenderViewport[0] = EyeRenderViewport[1] = FIntRect(0, 0, 0, 0); + + RenderTargetSize = FIntPoint(0, 0); +} + +TSharedPtr<FSettings, ESPMode::ThreadSafe> FSettings::Clone() const +{ + TSharedPtr<FSettings, ESPMode::ThreadSafe> NewSettings = MakeShareable(new FSettings(*this)); + return NewSettings; +} + +void FSettings::SetPixelDensity(float NewPixelDensity) +{ + if (Flags.bPixelDensityAdaptive) + { + PixelDensity = FMath::Clamp(NewPixelDensity, PixelDensityMin, PixelDensityMax); + } + else + { + PixelDensity = FMath::Clamp(NewPixelDensity, ClampPixelDensityMin, ClampPixelDensityMax); + } +} + +void FSettings::SetPixelDensityMin(float NewPixelDensityMin) +{ + PixelDensityMin = FMath::Clamp(NewPixelDensityMin, ClampPixelDensityMin, ClampPixelDensityMax); + PixelDensityMax = FMath::Max(PixelDensityMin, PixelDensityMax); + SetPixelDensity(PixelDensity); +} + +void FSettings::SetPixelDensityMax(float NewPixelDensityMax) +{ + PixelDensityMax = FMath::Clamp(NewPixelDensityMax, ClampPixelDensityMin, ClampPixelDensityMax); + PixelDensityMin = FMath::Min(PixelDensityMin, PixelDensityMax); + SetPixelDensity(PixelDensity); +} + + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Settings.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Settings.h new file mode 100644 index 0000000000000000000000000000000000000000..c0694962516b9edf22088d870fb3b5eebc1ad8ec --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Settings.h @@ -0,0 +1,151 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + +namespace OculusXRHMD +{ + +static const float ClampPixelDensityMin = 0.5f; +static const float ClampPixelDensityMax = 2.0f; + + +//------------------------------------------------------------------------------------------------- +// FSettings +//------------------------------------------------------------------------------------------------- + +class FSettings : public TSharedFromThis<FSettings, ESPMode::ThreadSafe> +{ +public: + union + { + struct + { + /** Whether stereo is currently on or off. */ + uint64 bStereoEnabled : 1; + + /** Whether or not switching to stereo is allowed */ + uint64 bHMDEnabled : 1; + + /** Turns on/off updating view's orientation/position on a RenderThread. When it is on, + latency should be significantly lower. + See 'HMD UPDATEONRT ON|OFF' console command. + */ + uint64 bUpdateOnRT : 1; + + /** Enforces headtracking to work even in non-stereo mode (for debugging or screenshots). + See 'MOTION ENFORCE' console command. */ + uint64 bHeadTrackingEnforced : 1; + + /** Allocate an high quality OVR_FORMAT_R11G11B10_FLOAT buffer for Rift */ + uint64 bHQBuffer : 1; + + /** Rendering should be (could be) paused */ + uint64 bPauseRendering : 1; + + /** HQ Distortion */ + uint64 bHQDistortion : 1; + + /** Send the depth buffer to the compositor */ + uint64 bCompositeDepth : 1; + + /** Supports Dash in-game compositing */ + uint64 bSupportsDash : 1; +#if !UE_BUILD_SHIPPING + /** Show status / statistics on screen. See 'hmd stats' cmd */ + uint64 bShowStats : 1; +#endif + /** Dynamically update pixel density to maintain framerate */ + uint64 bPixelDensityAdaptive : 1; + + /** All future eye buffers will need to be created with TexSRGB_Create flag due to the current feature level (ES31) */ + uint64 bsRGBEyeBuffer : 1; + + /** Supports Focus Aware state on Quest */ + uint64 bFocusAware : 1; + + /** Requires the Oculus system keyboard */ + uint64 bRequiresSystemKeyboard : 1; + + /** Whether passthrough functionality can be used with the app */ + uint64 bInsightPassthroughEnabled : 1; + + /** Whether Anchors and Scene can be used with the app */ + uint64 bAnchorSupportEnabled : 1; + + /** Whether body tracking functionality can be used with the app */ + uint64 bBodyTrackingEnabled : 1; + + /** Whether eye tracking functionality can be used with the app */ + uint64 bEyeTrackingEnabled : 1; + + /** Whether face tracking functionality can be used with the app */ + uint64 bFaceTrackingEnabled : 1; + }; + uint64 Raw; + } Flags; + + /** HMD base values, specify forward orientation and zero pos offset */ + FVector BaseOffset; // base position, in meters, relatively to the sensor //@todo hmd: clients need to stop using oculus space + FQuat BaseOrientation; // base orientation + + /** Viewports for each eye, in render target texture coordinates */ + FIntRect EyeRenderViewport[2]; + /** Viewports for each eye, without DynamicResolution scaling applied */ + FIntRect EyeUnscaledRenderViewport[2]; + + ovrpMatrix4f EyeProjectionMatrices[2]; // 0 - left, 1 - right same as Views + ovrpMatrix4f MonoProjectionMatrix; + + FIntPoint RenderTargetSize; + float PixelDensity; + float PixelDensityMin; + float PixelDensityMax; + + ovrpSystemHeadset SystemHeadset; + + float VsyncToNextVsync; + + EOculusXRProcessorPerformanceLevel SuggestedCpuPerfLevel; + EOculusXRProcessorPerformanceLevel SuggestedGpuPerfLevel; + + EOculusXRFoveatedRenderingMethod FoveatedRenderingMethod; + EOculusXRFoveatedRenderingLevel FoveatedRenderingLevel; + bool bDynamicFoveatedRendering; + bool bSupportEyeTrackedFoveatedRendering; + + EOculusXRXrApi XrApi; + EOculusXRColorSpace ColorSpace; + EOculusXRControllerPoseAlignment ControllerPoseAlignment; + + EOculusXRHandTrackingSupport HandTrackingSupport; + EOculusXRHandTrackingFrequency HandTrackingFrequency; + + ovrpVector4f ColorScale, ColorOffset; + bool bApplyColorScaleAndOffsetToAllLayers; + + EShaderPlatform CurrentShaderPlatform; + + bool bLateLatching; + bool bSupportExperimentalFeatures; + +public: + FSettings(); + virtual ~FSettings() {} + + bool IsStereoEnabled() const { return Flags.bStereoEnabled && Flags.bHMDEnabled; } + + void SetPixelDensity(float NewPixelDensity); + void SetPixelDensityMin(float NewPixelDensityMin); + void SetPixelDensityMax(float NewPixelDensityMax); + + TSharedPtr<FSettings, ESPMode::ThreadSafe> Clone() const; +}; + +typedef TSharedPtr<FSettings, ESPMode::ThreadSafe> FSettingsPtr; + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_SpectatorScreenController.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_SpectatorScreenController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..5350901ec5c27f64e1e2e2de0775b6f61123fe51 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_SpectatorScreenController.cpp @@ -0,0 +1,119 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_SpectatorScreenController.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD.h" +#include "Engine/TextureRenderTarget2D.h" + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FSpectatorScreenController +//------------------------------------------------------------------------------------------------- + +FSpectatorScreenController::FSpectatorScreenController(FOculusXRHMD* InOculusXRHMD) + : FDefaultSpectatorScreenController(InOculusXRHMD) + , OculusXRHMD(InOculusXRHMD) + , SpectatorMode(EMRSpectatorScreenMode::Default) + , ForegroundRenderTexture(nullptr) + , BackgroundRenderTexture(nullptr) +{ +} + +void FSpectatorScreenController::RenderSpectatorScreen_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture2D* BackBuffer, FTexture2DRHIRef RenderTexture, FVector2D WindowSize) +{ + CheckInRenderThread(); + if (OculusXRHMD->GetCustomPresent_Internal()) + { + if (SpectatorMode == EMRSpectatorScreenMode::ExternalComposition) + { + auto ForegroundResource = ForegroundRenderTexture->GetRenderTargetResource(); + auto BackgroundResource = BackgroundRenderTexture->GetRenderTargetResource(); + if (ForegroundResource && BackgroundResource) + { + RenderSpectatorModeExternalComposition( + RHICmdList, + FTexture2DRHIRef(BackBuffer), + ForegroundResource->GetRenderTargetTexture(), + BackgroundResource->GetRenderTargetTexture()); + return; + } + } + else if (SpectatorMode == EMRSpectatorScreenMode::DirectComposition) + { + auto BackgroundResource = BackgroundRenderTexture->GetRenderTargetResource(); + if (BackgroundResource) + { + RenderSpectatorModeDirectComposition( + RHICmdList, + FTexture2DRHIRef(BackBuffer), + BackgroundRenderTexture->GetRenderTargetResource()->GetRenderTargetTexture()); + return; + } + } + FDefaultSpectatorScreenController::RenderSpectatorScreen_RenderThread(RHICmdList, BackBuffer, RenderTexture, WindowSize); + } +} + +void FSpectatorScreenController::RenderSpectatorModeUndistorted(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, FTexture2DRHIRef EyeTexture, FTexture2DRHIRef OtherTexture, FVector2D WindowSize) +{ + CheckInRenderThread(); + FSettings* Settings = OculusXRHMD->GetSettings_RenderThread(); + FIntRect DestRect(0, 0, TargetTexture->GetSizeX() / 2, TargetTexture->GetSizeY()); + for (int i = 0; i < 2; ++i) + { + OculusXRHMD->CopyTexture_RenderThread(RHICmdList, EyeTexture, Settings->EyeRenderViewport[i], TargetTexture, DestRect, false, true); + DestRect.Min.X += TargetTexture->GetSizeX() / 2; + DestRect.Max.X += TargetTexture->GetSizeX() / 2; + } +} + +void FSpectatorScreenController::RenderSpectatorModeDistorted(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, FTexture2DRHIRef EyeTexture, FTexture2DRHIRef OtherTexture, FVector2D WindowSize) +{ + CheckInRenderThread(); + FCustomPresent* CustomPresent = OculusXRHMD->GetCustomPresent_Internal(); + FTexture2DRHIRef MirrorTexture = CustomPresent->GetMirrorTexture(); + if (MirrorTexture) + { + FIntRect SrcRect(0, 0, MirrorTexture->GetSizeX(), MirrorTexture->GetSizeY()); + FIntRect DestRect(0, 0, TargetTexture->GetSizeX(), TargetTexture->GetSizeY()); + OculusXRHMD->CopyTexture_RenderThread(RHICmdList, MirrorTexture, SrcRect, TargetTexture, DestRect, false, true); + } +} + +void FSpectatorScreenController::RenderSpectatorModeSingleEye(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, FTexture2DRHIRef EyeTexture, FTexture2DRHIRef OtherTexture, FVector2D WindowSize) +{ + CheckInRenderThread(); + FSettings* Settings = OculusXRHMD->GetSettings_RenderThread(); + const FIntRect SrcRect= Settings->EyeRenderViewport[0]; + const FIntRect DstRect(0, 0, TargetTexture->GetSizeX(), TargetTexture->GetSizeY()); + + OculusXRHMD->CopyTexture_RenderThread(RHICmdList, EyeTexture, SrcRect, TargetTexture, DstRect, false, true); +} + +void FSpectatorScreenController::RenderSpectatorModeDirectComposition(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, const FTexture2DRHIRef SrcTexture) const +{ + CheckInRenderThread(); + const FIntRect SrcRect(0, 0, SrcTexture->GetSizeX(), SrcTexture->GetSizeY()); + const FIntRect DstRect(0, 0, TargetTexture->GetSizeX(), TargetTexture->GetSizeY()); + + OculusXRHMD->CopyTexture_RenderThread(RHICmdList, SrcTexture, SrcRect, TargetTexture, DstRect, false, true); +} + +void FSpectatorScreenController::RenderSpectatorModeExternalComposition(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, const FTexture2DRHIRef FrontTexture, const FTexture2DRHIRef BackTexture) const +{ + CheckInRenderThread(); + const FIntRect FrontSrcRect(0, 0, FrontTexture->GetSizeX(), FrontTexture->GetSizeY()); + const FIntRect FrontDstRect(0, 0, TargetTexture->GetSizeX() / 2, TargetTexture->GetSizeY()); + const FIntRect BackSrcRect(0, 0, BackTexture->GetSizeX(), BackTexture->GetSizeY()); + const FIntRect BackDstRect(TargetTexture->GetSizeX() / 2, 0, TargetTexture->GetSizeX(), TargetTexture->GetSizeY()); + + OculusXRHMD->CopyTexture_RenderThread(RHICmdList, FrontTexture, FrontSrcRect, TargetTexture, FrontDstRect, false, true); + OculusXRHMD->CopyTexture_RenderThread(RHICmdList, BackTexture, BackSrcRect, TargetTexture, BackDstRect, false, true); +} + +} // namespace OculusXRHMD + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_SpectatorScreenController.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_SpectatorScreenController.h new file mode 100644 index 0000000000000000000000000000000000000000..a3b4e327ee594c474208e5748032b36a6dface1f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_SpectatorScreenController.h @@ -0,0 +1,52 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "DefaultSpectatorScreenController.h" + +class UTextureRenderTarget2D; + +namespace OculusXRHMD +{ + +// Oculus specific spectator screen modes that override the regular VR spectator screens +enum class EMRSpectatorScreenMode : uint8 +{ + Default, + ExternalComposition, + DirectComposition +}; + +//------------------------------------------------------------------------------------------------- +// FSpectatorScreenController +//------------------------------------------------------------------------------------------------- + +class FSpectatorScreenController : public FDefaultSpectatorScreenController +{ +public: + FSpectatorScreenController(class FOculusXRHMD* InOculusXRHMD); + + void SetMRSpectatorScreenMode(EMRSpectatorScreenMode Mode){ SpectatorMode = Mode; } + void SetMRForeground(UTextureRenderTarget2D* Texture) { ForegroundRenderTexture = Texture; } + void SetMRBackground(UTextureRenderTarget2D* Texture) { BackgroundRenderTexture = Texture; } + + virtual void RenderSpectatorScreen_RenderThread(FRHICommandListImmediate& RHICmdList, FRHITexture2D* BackBuffer, FTexture2DRHIRef RenderTarget, FVector2D WindowSize) override; + virtual void RenderSpectatorModeUndistorted(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, FTexture2DRHIRef EyeTexture, FTexture2DRHIRef OtherTexture, FVector2D WindowSize) override; + virtual void RenderSpectatorModeDistorted(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, FTexture2DRHIRef EyeTexture, FTexture2DRHIRef OtherTexture, FVector2D WindowSize) override; + virtual void RenderSpectatorModeSingleEye(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, FTexture2DRHIRef EyeTexture, FTexture2DRHIRef OtherTexture, FVector2D WindowSize) override; +private: + FOculusXRHMD* OculusXRHMD; + EMRSpectatorScreenMode SpectatorMode; + UTextureRenderTarget2D* ForegroundRenderTexture; + UTextureRenderTarget2D* BackgroundRenderTexture; + + void RenderSpectatorModeDirectComposition(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, const FTexture2DRHIRef SrcTexture) const; + void RenderSpectatorModeExternalComposition(FRHICommandListImmediate& RHICmdList, FTexture2DRHIRef TargetTexture, const FTexture2DRHIRef FrontTexture, const FTexture2DRHIRef BackTexture) const; +}; + + +} // namespace OculusXRHMD + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Splash.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Splash.cpp new file mode 100644 index 0000000000000000000000000000000000000000..33775bf523905263aff5e61e370d6bd91ade5b97 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Splash.cpp @@ -0,0 +1,679 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_Splash.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD.h" +#include "RenderingThread.h" +#include "Misc/ScopeLock.h" +#include "OculusXRHMDRuntimeSettings.h" +#include "StereoLayerFunctionLibrary.h" +#if PLATFORM_ANDROID +#include "Android/AndroidJNI.h" +#include "Android/AndroidEGL.h" +#include "Android/AndroidApplication.h" +#include "OculusXRHMDTypes.h" +#endif + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FSplash +//------------------------------------------------------------------------------------------------- + +FSplash::FSplash(FOculusXRHMD* InOculusXRHMD) : + OculusXRHMD(InOculusXRHMD), + CustomPresent(InOculusXRHMD->GetCustomPresent_Internal()), + FramesOutstanding(0), + NextLayerId(1), + bInitialized(false), + bIsShown(false), + bNeedSplashUpdate(false), + bShouldShowSplash(false), + SystemDisplayInterval(1 / 90.0f) +{ + // Create empty quad layer for UE layer + { + IStereoLayers::FLayerDesc LayerDesc; + LayerDesc.QuadSize = FVector2D(0.01f, 0.01f); + LayerDesc.Priority = 0; + LayerDesc.PositionType = IStereoLayers::TrackerLocked; + LayerDesc.Texture = nullptr; + UELayer = MakeShareable(new FLayer(NextLayerId++, LayerDesc)); + } +} + +FSplash::~FSplash() +{ + // Make sure RenTicker is freed in Shutdown + check(!Ticker.IsValid()) +} + +void FSplash::Tick_RenderThread(float DeltaTime) +{ + CheckInRenderThread(); + + if (FramesOutstanding > 0) + { + UE_LOG(LogHMD, VeryVerbose, TEXT("Splash skipping frame; too many frames outstanding")); + return; + } + + const double TimeInSeconds = FPlatformTime::Seconds(); + const double DeltaTimeInSeconds = TimeInSeconds - LastTimeInSeconds; + + if (DeltaTimeInSeconds > 2.f * SystemDisplayInterval && Layers_RenderThread_DeltaRotation.Num() > 0) + { + FScopeLock ScopeLock(&RenderThreadLock); + for (TTuple<FLayerPtr, FQuat>& Info : Layers_RenderThread_DeltaRotation) + { + FLayerPtr Layer = Info.Key; + const FQuat& DeltaRotation = Info.Value; + check(Layer.IsValid()); + check(!DeltaRotation.Equals(FQuat::Identity)); // Only layers with non-zero delta rotation should be in the DeltaRotation array. + + IStereoLayers::FLayerDesc LayerDesc = Layer->GetDesc(); + LayerDesc.Transform.SetRotation(LayerDesc.Transform.GetRotation() * DeltaRotation); + LayerDesc.Transform.NormalizeRotation(); + Layer->SetDesc(LayerDesc); + } + LastTimeInSeconds = TimeInSeconds; + } + + RenderFrame_RenderThread(FRHICommandListExecutor::GetImmediateCommandList()); +} + +void FSplash::LoadSettings() +{ + UOculusXRHMDRuntimeSettings* HMDSettings = GetMutableDefault<UOculusXRHMDRuntimeSettings>(); + check(HMDSettings); + ClearSplashes(); + for (const FOculusXRSplashDesc& SplashDesc : HMDSettings->SplashDescs) + { + AddSplash(SplashDesc); + } + + if (HMDSettings->bAutoEnabled) + { + if (!PreLoadLevelDelegate.IsValid()) + { + PreLoadLevelDelegate = FCoreUObjectDelegates::PreLoadMap.AddSP(this, &FSplash::OnPreLoadMap); + } + if (!PostLoadLevelDelegate.IsValid()) + { + PostLoadLevelDelegate = FCoreUObjectDelegates::PostLoadMapWithWorld.AddSP(this, &FSplash::OnPostLoadMap); + } + } + else + { + if (PreLoadLevelDelegate.IsValid()) + { + FCoreUObjectDelegates::PreLoadMap.Remove(PreLoadLevelDelegate); + PreLoadLevelDelegate.Reset(); + } + if (PostLoadLevelDelegate.IsValid()) + { + FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(PostLoadLevelDelegate); + PostLoadLevelDelegate.Reset(); + } + } +} + +void FSplash::OnPreLoadMap(const FString&) +{ + DoShow(); +} + +void FSplash::OnPostLoadMap(UWorld* LoadedWorld) +{ + // Don't auto-hide splash if show loading screen is called explicitly + if (!bShouldShowSplash) + { + UE_LOG(LogHMD, Log, TEXT("FSplash::OnPostLoadMap Hide Auto Splash")); + HideLoadingScreen(); + } +} + +#if WITH_EDITOR +void FSplash::OnPieBegin(bool bIsSimulating) +{ + LoadSettings(); +} +#endif + +void FSplash::Startup() +{ + CheckInGameThread(); + + if (!bInitialized) + { + Settings = OculusXRHMD->CreateNewSettings(); + Frame = OculusXRHMD->CreateNewGameFrame(); + // keep units in meters rather than UU (because UU make not much sense). + Frame->WorldToMetersScale = 1.0f; + + float SystemDisplayFrequency; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetSystemDisplayFrequency2(&SystemDisplayFrequency))) + { + SystemDisplayInterval = 1.0f / SystemDisplayFrequency; + } + + LoadSettings(); + + OculusXRHMD->InitDevice(); + +#if WITH_EDITOR + PieBeginDelegateHandle = FEditorDelegates::BeginPIE.AddRaw(this, &FSplash::OnPieBegin); +#else + UOculusXRHMDRuntimeSettings* HMDSettings = GetMutableDefault<UOculusXRHMDRuntimeSettings>(); + check(HMDSettings); + if (HMDSettings->bAutoEnabled) + { + UE_LOG(LogHMD, Log, TEXT("FSplash::Startup Show Splash on Startup")); + DoShow(); + } +#endif + + bInitialized = true; + } +} + +void FSplash::StopTicker() +{ + CheckInGameThread(); + + if (!bIsShown) + { + ExecuteOnRenderThread([this]() + { + if (Ticker.IsValid()) + { + Ticker->Unregister(); + Ticker = nullptr; + } + }); + UnloadTextures(); + } +} + +void FSplash::StartTicker() +{ + CheckInGameThread(); + + if (!Ticker.IsValid()) + { + Ticker = MakeShareable(new FTicker(this)); + + ExecuteOnRenderThread([this]() + { + LastTimeInSeconds = FPlatformTime::Seconds(); + Ticker->Register(); + }); + } + +} + +void FSplash::RenderFrame_RenderThread(FRHICommandListImmediate& RHICmdList) +{ + CheckInRenderThread(); + + FScopeLock ScopeLock(&RenderThreadLock); + + // RenderFrame + FSettingsPtr XSettings = Settings->Clone(); + FGameFramePtr XFrame = Frame->Clone(); + XFrame->FrameNumber = OculusXRHMD->NextFrameNumber; + XFrame->ShowFlags.Rendering = true; + TArray<FLayerPtr> XLayers = Layers_RenderThread_Input; + + ensure(XLayers.Num() != 0); + + ovrpResult Result; + if ( FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && OculusXRHMD->WaitFrameNumber != XFrame->FrameNumber) + { + UE_LOG(LogHMD, Verbose, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame %u"), XFrame->FrameNumber); + if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame(XFrame->FrameNumber))) + { + UE_LOG(LogHMD, Error, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().WaitToBeginFrame %u failed (%d)"), XFrame->FrameNumber, Result); + XFrame->ShowFlags.Rendering = false; + } + else + { + OculusXRHMD->WaitFrameNumber = XFrame->FrameNumber; + OculusXRHMD->NextFrameNumber = XFrame->FrameNumber + 1; + FPlatformAtomics::InterlockedIncrement(&FramesOutstanding); + } + } + else + { + XFrame->ShowFlags.Rendering = false; + } + + if (XFrame->ShowFlags.Rendering) + { + if (OVRP_FAILURE(Result = FOculusXRHMDModule::GetPluginWrapper().Update3(ovrpStep_Render, XFrame->FrameNumber, 0.0))) + { + UE_LOG(LogHMD, Error, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().Update3 %u failed (%d)"), XFrame->FrameNumber, Result); + } + } + + { + int32 LayerIndex = 0; + int32 LayerIndex_RenderThread = 0; + + while(LayerIndex < XLayers.Num() && LayerIndex_RenderThread < Layers_RenderThread.Num()) + { + uint32 LayerIdA = XLayers[LayerIndex]->GetId(); + uint32 LayerIdB = Layers_RenderThread[LayerIndex_RenderThread]->GetId(); + + if (LayerIdA < LayerIdB) + { + XLayers[LayerIndex++]->Initialize_RenderThread(XSettings.Get(), CustomPresent, &OculusXRHMD->DeferredDeletion, RHICmdList); + } + else if (LayerIdA > LayerIdB) + { + OculusXRHMD->DeferredDeletion.AddLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]); + } + else + { + XLayers[LayerIndex++]->Initialize_RenderThread(XSettings.Get(), CustomPresent, &OculusXRHMD->DeferredDeletion, RHICmdList, Layers_RenderThread[LayerIndex_RenderThread++].Get()); + } + } + + while(LayerIndex < XLayers.Num()) + { + XLayers[LayerIndex++]->Initialize_RenderThread(XSettings.Get(), CustomPresent, &OculusXRHMD->DeferredDeletion, RHICmdList); + } + + while (LayerIndex_RenderThread < Layers_RenderThread.Num()) + { + OculusXRHMD->DeferredDeletion.AddLayerToDeferredDeletionQueue(Layers_RenderThread[LayerIndex_RenderThread++]); + } + } + + Layers_RenderThread = XLayers; + + for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++) + { + Layers_RenderThread[LayerIndex]->UpdateTexture_RenderThread(CustomPresent, RHICmdList); + } + + // This submit is required since splash happens before the game is rendering, so layers won't be submitted with game render commands + CustomPresent->SubmitGPUCommands_RenderThread(RHICmdList); + + // RHIFrame + for (int32 LayerIndex = 0; LayerIndex < XLayers.Num(); LayerIndex++) + { + XLayers[LayerIndex] = XLayers[LayerIndex]->Clone(); + } + + ExecuteOnRHIThread_DoNotWait([this, XSettings, XFrame, XLayers]() + { + ovrpResult ResultT; + + if (XFrame->ShowFlags.Rendering) + { + UE_LOG(LogHMD, Verbose, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().BeginFrame4 %u"), XFrame->FrameNumber); + if (OVRP_FAILURE(ResultT = FOculusXRHMDModule::GetPluginWrapper().BeginFrame4(XFrame->FrameNumber, CustomPresent->GetOvrpCommandQueue()))) + { + UE_LOG(LogHMD, Error, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().BeginFrame4 %u failed (%d)"), XFrame->FrameNumber, ResultT); + XFrame->ShowFlags.Rendering = false; + } + } + + FPlatformAtomics::InterlockedDecrement(&FramesOutstanding); + + Layers_RHIThread = XLayers; + Layers_RHIThread.Sort(FLayerPtr_ComparePriority()); + + if (XFrame->ShowFlags.Rendering) + { + TArray<const ovrpLayerSubmit*> LayerSubmitPtr; + LayerSubmitPtr.SetNum(Layers_RHIThread.Num()); + + for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++) + { + LayerSubmitPtr[LayerIndex] = Layers_RHIThread[LayerIndex]->UpdateLayer_RHIThread(XSettings.Get(), XFrame.Get(), LayerIndex); + } + + UE_LOG(LogHMD, Verbose, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().EndFrame4 %u"), XFrame->FrameNumber); + if (OVRP_FAILURE(ResultT = FOculusXRHMDModule::GetPluginWrapper().EndFrame4(XFrame->FrameNumber, LayerSubmitPtr.GetData(), LayerSubmitPtr.Num(), CustomPresent->GetOvrpCommandQueue()))) + { + UE_LOG(LogHMD, Error, TEXT("Splash FOculusXRHMDModule::GetPluginWrapper().EndFrame4 %u failed (%d)"), XFrame->FrameNumber, ResultT); + } + else + { + for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++) + { + Layers_RHIThread[LayerIndex]->IncrementSwapChainIndex_RHIThread(CustomPresent); + } + } + } + }); +} + +void FSplash::ReleaseResources_RHIThread() +{ + for (int32 LayerIndex = 0; LayerIndex < Layers_RenderThread.Num(); LayerIndex++) + { + Layers_RenderThread[LayerIndex]->ReleaseResources_RHIThread(); + } + + for (int32 LayerIndex = 0; LayerIndex < Layers_RHIThread.Num(); LayerIndex++) + { + Layers_RHIThread[LayerIndex]->ReleaseResources_RHIThread(); + } + + Layers_RenderThread.Reset(); + Layers_RHIThread.Reset(); +} + + +void FSplash::PreShutdown() +{ + CheckInGameThread(); +} + + +void FSplash::Shutdown() +{ + CheckInGameThread(); + +#if WITH_EDITOR + if (PieBeginDelegateHandle.IsValid()) + { + FEditorDelegates::BeginPIE.Remove(PieBeginDelegateHandle); + PieBeginDelegateHandle.Reset(); + } +#endif + + if (PreLoadLevelDelegate.IsValid()) + { + FCoreUObjectDelegates::PreLoadMap.Remove(PreLoadLevelDelegate); + PreLoadLevelDelegate.Reset(); + } + if (PostLoadLevelDelegate.IsValid()) + { + FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(PostLoadLevelDelegate); + PostLoadLevelDelegate.Reset(); + } + + if (bInitialized) + { + ExecuteOnRenderThread([this]() + { + if(Ticker) + { + Ticker->Unregister(); + Ticker = nullptr; + } + + ExecuteOnRHIThread([this]() + { + SplashLayers.Reset(); + Layers_RenderThread.Reset(); + Layers_RenderThread_Input.Reset(); + Layers_RHIThread.Reset(); + }); + }); + + bInitialized = false; + } +} + +int FSplash::AddSplash(const FOculusXRSplashDesc& Desc) +{ + CheckInGameThread(); + + FScopeLock ScopeLock(&RenderThreadLock); + return SplashLayers.Add(FSplashLayer(Desc)); +} + + +void FSplash::AddSplash(const FSplashDesc& Splash) +{ + FOculusXRSplashDesc OculusDesc; + OculusDesc.TransformInMeters = Splash.Transform; + OculusDesc.QuadSizeInMeters = Splash.QuadSize; + OculusDesc.DeltaRotation = Splash.DeltaRotation; + OculusDesc.bNoAlphaChannel = Splash.bIgnoreAlpha; + OculusDesc.bIsDynamic = Splash.bIsDynamic || Splash.bIsExternal; + OculusDesc.TextureOffset = Splash.UVRect.Min; + OculusDesc.TextureScale = Splash.UVRect.Max; + OculusDesc.LoadedTexture = Splash.Texture; + + AddSplash(OculusDesc); +} + +void FSplash::ClearSplashes() +{ + CheckInGameThread(); + + FScopeLock ScopeLock(&RenderThreadLock); + SplashLayers.Reset(); +} + + +bool FSplash::GetSplash(unsigned InSplashLayerIndex, FOculusXRSplashDesc& OutDesc) +{ + CheckInGameThread(); + + FScopeLock ScopeLock(&RenderThreadLock); + if (InSplashLayerIndex < unsigned(SplashLayers.Num())) + { + OutDesc = SplashLayers[int32(InSplashLayerIndex)].Desc; + return true; + } + return false; +} + +IStereoLayers::FLayerDesc FSplash::StereoLayerDescFromOculusSplashDesc(FOculusXRSplashDesc OculusDesc) +{ + IStereoLayers::FLayerDesc LayerDesc; + if (OculusDesc.LoadedTexture->GetTextureCube() != nullptr) + { + LayerDesc.SetShape<FCubemapLayer>(); + } + // else LayerDesc.Shape defaults to FQuadLayer + + LayerDesc.Transform = OculusDesc.TransformInMeters * FTransform(OculusXRHMD->GetSplashRotation().Quaternion()); + LayerDesc.QuadSize = OculusDesc.QuadSizeInMeters; + LayerDesc.UVRect = FBox2D(OculusDesc.TextureOffset, OculusDesc.TextureOffset + OculusDesc.TextureScale); + LayerDesc.Priority = INT32_MAX - (int32)(OculusDesc.TransformInMeters.GetTranslation().X * 1000.f); + LayerDesc.PositionType = IStereoLayers::TrackerLocked; + LayerDesc.Texture = OculusDesc.LoadedTexture; + LayerDesc.Flags = IStereoLayers::LAYER_FLAG_QUAD_PRESERVE_TEX_RATIO | (OculusDesc.bNoAlphaChannel ? IStereoLayers::LAYER_FLAG_TEX_NO_ALPHA_CHANNEL : 0) | (OculusDesc.bIsDynamic ? IStereoLayers::LAYER_FLAG_TEX_CONTINUOUS_UPDATE : 0); + + return LayerDesc; +} + +void FSplash::DoShow() +{ + CheckInGameThread(); + + OculusXRHMD->SetSplashRotationToForward(); + + // Create new textures + UnloadTextures(); + + // Make sure all UTextures are loaded and contain Resource->TextureRHI + bool bWaitForRT = false; + + for (int32 SplashLayerIndex = 0; SplashLayerIndex < SplashLayers.Num(); ++SplashLayerIndex) + { + FSplashLayer& SplashLayer = SplashLayers[SplashLayerIndex]; + + if (SplashLayer.Desc.TexturePath.IsValid()) + { + // load temporary texture (if TexturePath was specified) + LoadTexture(SplashLayer); + } + if (SplashLayer.Desc.LoadingTexture && SplashLayer.Desc.LoadingTexture->IsValidLowLevel()) + { + SplashLayer.Desc.LoadingTexture->UpdateResource(); + bWaitForRT = true; + } + } + + FlushRenderingCommands(); + + for (int32 SplashLayerIndex = 0; SplashLayerIndex < SplashLayers.Num(); ++SplashLayerIndex) + { + FSplashLayer& SplashLayer = SplashLayers[SplashLayerIndex]; + + //@DBG BEGIN + if (SplashLayer.Desc.LoadingTexture->IsValidLowLevel()) + { + if (SplashLayer.Desc.LoadingTexture->GetResource() && SplashLayer.Desc.LoadingTexture->GetResource()->TextureRHI) + { + SplashLayer.Desc.LoadedTexture = SplashLayer.Desc.LoadingTexture->GetResource()->TextureRHI; + } + else + { + UE_LOG(LogHMD, Warning, TEXT("Splash, %s - no Resource"), *SplashLayer.Desc.LoadingTexture->GetDesc()); + } + } + //@DBG END + + if (SplashLayer.Desc.LoadedTexture) + { + SplashLayer.Layer = MakeShareable(new FLayer(NextLayerId++, StereoLayerDescFromOculusSplashDesc(SplashLayer.Desc))); + } + } + + { + //add oculus-generated layers through the OculusVR settings area + FScopeLock ScopeLock(&RenderThreadLock); + Layers_RenderThread_DeltaRotation.Reset(); + Layers_RenderThread_Input.Reset(); + for (int32 SplashLayerIndex = 0; SplashLayerIndex < SplashLayers.Num(); SplashLayerIndex++) + { + const FSplashLayer& SplashLayer = SplashLayers[SplashLayerIndex]; + + if (SplashLayer.Layer.IsValid()) + { + FLayerPtr ClonedLayer = SplashLayer.Layer->Clone(); + Layers_RenderThread_Input.Add(ClonedLayer); + + // Register layers that need to be rotated every n ticks + if (!SplashLayer.Desc.DeltaRotation.Equals(FQuat::Identity)) + { + Layers_RenderThread_DeltaRotation.Emplace(ClonedLayer, SplashLayer.Desc.DeltaRotation); + } + } + } + + //add UE VR splash screen + FOculusXRSplashDesc UESplashDesc = OculusXRHMD->GetUESplashScreenDesc(); + if (UESplashDesc.LoadedTexture != nullptr) + { + UELayer.Reset(); + UELayer = MakeShareable(new FLayer(NextLayerId++, StereoLayerDescFromOculusSplashDesc(UESplashDesc))); + Layers_RenderThread_Input.Add(UELayer->Clone()); + } + + Layers_RenderThread_Input.Sort(FLayerPtr_CompareId()); + } + + if (Layers_RenderThread_Input.Num() > 0) + { + // If no textures are loaded, this will push black frame + StartTicker(); + bIsShown = true; + UE_LOG(LogHMD, Log, TEXT("FSplash::DoShow")); + } + else + { + UE_LOG(LogHMD, Log, TEXT("No splash layers in FSplash::DoShow")); + } +} + +void FSplash::DoHide() +{ + CheckInGameThread(); + + UE_LOG(LogHMD, Log, TEXT("FSplash::DoHide")); + bIsShown = false; + + StopTicker(); +} + +void FSplash::UpdateLoadingScreen_GameThread() +{ + if (bNeedSplashUpdate) + { + if (bShouldShowSplash) + { + DoShow(); + } + else + { + DoHide(); + } + + bNeedSplashUpdate = false; + } +} + +void FSplash::ShowLoadingScreen() +{ + bShouldShowSplash = true; + + // DoShow will be called from UpdateSplashScreen_Gamethread(). + // This can can happen if the splashes are already being shown, as it will reset the relative positions and delta rotations of the layers. + bNeedSplashUpdate = true; +} + +void FSplash::HideLoadingScreen() +{ + bShouldShowSplash = false; + bNeedSplashUpdate = bIsShown; // no need to call DoHide when the splash is already hidden +} + +void FSplash::UnloadTextures() +{ + CheckInGameThread(); + + // unload temporary loaded textures + FScopeLock ScopeLock(&RenderThreadLock); + for (int32 SplashLayerIndex = 0; SplashLayerIndex < SplashLayers.Num(); ++SplashLayerIndex) + { + if (SplashLayers[SplashLayerIndex].Desc.TexturePath.IsValid()) + { + UnloadTexture(SplashLayers[SplashLayerIndex]); + } + } +} + + +void FSplash::LoadTexture(FSplashLayer& InSplashLayer) +{ + CheckInGameThread(); + + UnloadTexture(InSplashLayer); + + UE_LOG(LogLoadingSplash, Log, TEXT("Loading texture for splash %s..."), *InSplashLayer.Desc.TexturePath.GetAssetName()); + InSplashLayer.Desc.LoadingTexture = Cast<UTexture>(InSplashLayer.Desc.TexturePath.TryLoad()); + if (InSplashLayer.Desc.LoadingTexture != nullptr) + { + UE_LOG(LogLoadingSplash, Log, TEXT("...Success. ")); + } + InSplashLayer.Desc.LoadedTexture = nullptr; + InSplashLayer.Layer.Reset(); +} + + +void FSplash::UnloadTexture(FSplashLayer& InSplashLayer) +{ + CheckInGameThread(); + + InSplashLayer.Desc.LoadingTexture = nullptr; + InSplashLayer.Desc.LoadedTexture = nullptr; + InSplashLayer.Layer.Reset(); +} + + +} // namespace OculusXRHMD + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Splash.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Splash.h new file mode 100644 index 0000000000000000000000000000000000000000..87036a01bfc4ab547090aad52b05187112882282 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_Splash.h @@ -0,0 +1,138 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" +#include "IXRLoadingScreen.h" + +#if WITH_EDITOR +#include "Editor.h" +#endif + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMD_GameFrame.h" +#include "OculusXRHMD_Layer.h" +#include "TickableObjectRenderThread.h" +#include "OculusXRHMDTypes.h" + +namespace OculusXRHMD +{ + +class FOculusXRHMD; + +//------------------------------------------------------------------------------------------------- +// FSplashLayer +//------------------------------------------------------------------------------------------------- + +struct FSplashLayer +{ + FOculusXRSplashDesc Desc; + FLayerPtr Layer; + +public: + FSplashLayer(const FOculusXRSplashDesc& InDesc) : Desc(InDesc) {} + FSplashLayer(const FSplashLayer& InSplashLayer) : Desc(InSplashLayer.Desc), Layer(InSplashLayer.Layer) {} +}; + + +//------------------------------------------------------------------------------------------------- +// FSplash +//------------------------------------------------------------------------------------------------- + +class FSplash : public IXRLoadingScreen, public TSharedFromThis<FSplash> +{ +protected: + class FTicker : public FTickableObjectRenderThread, public TSharedFromThis<FTicker> + { + public: + FTicker(FSplash* InSplash) : FTickableObjectRenderThread(false, true), pSplash(InSplash) {} + + virtual void Tick(float DeltaTime) override { pSplash->Tick_RenderThread(DeltaTime); } + virtual TStatId GetStatId() const override { RETURN_QUICK_DECLARE_CYCLE_STAT(FSplash, STATGROUP_Tickables); } + virtual bool IsTickable() const override { return true; } + + protected: + FSplash* pSplash; + }; + +public: + FSplash(FOculusXRHMD* InPlugin); + virtual ~FSplash(); + + void Tick_RenderThread(float DeltaTime); + + void Startup(); + void LoadSettings(); + void ReleaseResources_RHIThread(); + void PreShutdown(); + void Shutdown(); + + void OnPreLoadMap(const FString&); + void OnPostLoadMap(UWorld* LoadedWorld); +#if WITH_EDITOR + void OnPieBegin(bool bIsSimulating); +#endif + + // Called from FOculusXRHMD + void UpdateLoadingScreen_GameThread(); + + // Internal extended API + int AddSplash(const FOculusXRSplashDesc&); + bool GetSplash(unsigned index, FOculusXRSplashDesc& OutDesc); + void StopTicker(); + void StartTicker(); + + // The standard IXRLoadingScreen interface + virtual void ShowLoadingScreen() override; + virtual void HideLoadingScreen() override; + virtual void ClearSplashes() override; + virtual void AddSplash(const FSplashDesc& Splash) override; + virtual bool IsShown() const override { return bIsShown; } + +protected: + void DoShow(); + void DoHide(); + void UnloadTextures(); + void LoadTexture(FSplashLayer& InSplashLayer); + void UnloadTexture(FSplashLayer& InSplashLayer); + + void RenderFrame_RenderThread(FRHICommandListImmediate& RHICmdList); + IStereoLayers::FLayerDesc StereoLayerDescFromOculusSplashDesc(FOculusXRSplashDesc OculusDesc); + +protected: + FOculusXRHMD* OculusXRHMD; + FCustomPresent* CustomPresent; + TSharedPtr<FTicker> Ticker; + int32 FramesOutstanding; + FCriticalSection RenderThreadLock; + FSettingsPtr Settings; + FGameFramePtr Frame; + TArray<FSplashLayer> SplashLayers; + uint32 NextLayerId; + FLayerPtr BlackLayer; + FLayerPtr UELayer; + TArray<TTuple<FLayerPtr, FQuat>> Layers_RenderThread_DeltaRotation; + TArray<FLayerPtr> Layers_RenderThread_Input; + TArray<FLayerPtr> Layers_RenderThread; + TArray<FLayerPtr> Layers_RHIThread; + + // All these flags are only modified from the Game thread + bool bInitialized; + bool bIsShown; + bool bNeedSplashUpdate; + bool bShouldShowSplash; + + float SystemDisplayInterval; + double LastTimeInSeconds; + FDelegateHandle PreLoadLevelDelegate; + FDelegateHandle PostLoadLevelDelegate; +#if WITH_EDITOR + FDelegateHandle PieBeginDelegateHandle; +#endif +}; + +typedef TSharedPtr<FSplash> FSplashPtr; + + +} // namespace OculusXRHMD + +#endif // OCULUS_HMD_SUPPORTED_PLATFORMS \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_StressTester.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_StressTester.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d90c094698d79036543d9fcc6769bda3647f2406 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_StressTester.cpp @@ -0,0 +1,324 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_StressTester.h" + +#if OCULUS_STRESS_TESTS_ENABLED +#include "OculusXRHMD.h" +#include "GlobalShader.h" +#include "UniformBuffer.h" +#include "RHICommandList.h" +#include "ShaderParameterUtils.h" +#include "RHIStaticStates.h" +#include "PipelineStateCache.h" +#include "OculusShaders.h" +#include "SceneUtils.h" // for SCOPED_DRAW_EVENT() + +DECLARE_STATS_GROUP(TEXT("Oculus"), STATGROUP_Oculus, STATCAT_Advanced); +DECLARE_CYCLE_STAT(TEXT("GPUStressRendering"), STAT_GPUStressRendering, STATGROUP_Oculus); + +//------------------------------------------------------------------------------------------------- +// Uniform buffers +//------------------------------------------------------------------------------------------------- + +//This buffer should contain variables that never, or rarely change +BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FOculusPixelShaderConstantParameters, ) +//SHADER_PARAMETER(FVector4, Name) +END_GLOBAL_SHADER_PARAMETER_STRUCT() + +IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FOculusPixelShaderConstantParameters, "PSConstants"); + +typedef TUniformBufferRef<FOculusPixelShaderConstantParameters> FOculusPixelShaderConstantParametersRef; + + +//This buffer is for variables that change very often (each frame for example) +BEGIN_GLOBAL_SHADER_PARAMETER_STRUCT(FOculusPixelShaderVariableParameters, ) +SHADER_PARAMETER(int, IterationsMultiplier) +END_GLOBAL_SHADER_PARAMETER_STRUCT() + +IMPLEMENT_GLOBAL_SHADER_PARAMETER_STRUCT(FOculusPixelShaderVariableParameters, "PSVariables"); + +typedef TUniformBufferRef<FOculusPixelShaderVariableParameters> FOculusPixelShaderVariableParametersRef; + +namespace OculusXRHMD +{ + + +//------------------------------------------------------------------------------------------------- +// FTextureVertexDeclaration +//------------------------------------------------------------------------------------------------- + +struct FTextureVertex +{ + FVector4 Position; + FVector2f UV; +}; + +inline FBufferRHIRef CreateTempOcculusVertexBuffer() +{ + FRHIResourceCreateInfo CreateInfo(TEXT("TempOcculusVertexBuffer")); + FBufferRHIRef VertexBufferRHI = RHICreateVertexBuffer(sizeof(FTextureVertex) * 4, BUF_Volatile, CreateInfo); + void* VoidPtr = RHILockBuffer(VertexBufferRHI, 0, sizeof(FTextureVertex) * 4, RLM_WriteOnly); + + FTextureVertex* Vertices = (FTextureVertex*)VoidPtr; + Vertices[0].Position = FVector4(-1.0f, 1.0f, 0, 1.0f); + Vertices[1].Position = FVector4(1.0f, 1.0f, 0, 1.0f); + Vertices[2].Position = FVector4(-1.0f, -1.0f, 0, 1.0f); + Vertices[3].Position = FVector4(1.0f, -1.0f, 0, 1.0f); + Vertices[0].UV = FVector2f(0, 0); + Vertices[1].UV = FVector2f(1, 0); + Vertices[2].UV = FVector2f(0, 1); + Vertices[3].UV = FVector2f(1, 1); + RHIUnlockBuffer(VertexBufferRHI); + + return VertexBufferRHI; +} + +class FTextureVertexDeclaration : public FRenderResource +{ +public: + FVertexDeclarationRHIRef VertexDeclarationRHI; + + virtual void InitRHI() override + { + FVertexDeclarationElementList Elements; + uint32 Stride = sizeof(FTextureVertex); + Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, Position), VET_Float4, 0, Stride)); + Elements.Add(FVertexElement(0, STRUCT_OFFSET(FTextureVertex, UV), VET_Float2, 1, Stride)); + VertexDeclarationRHI = PipelineStateCache::GetOrCreateVertexDeclaration(Elements); + } + + virtual void ReleaseRHI() override + { + VertexDeclarationRHI.SafeRelease(); + } +}; + +static TGlobalResource<FTextureVertexDeclaration> GOculusTextureVertexDeclaration; + + +//------------------------------------------------------------------------------------------------- +// FStressTester +//------------------------------------------------------------------------------------------------- + +TSharedPtr<FStressTester, ESPMode::ThreadSafe> FStressTester::SharedInstance; + +TSharedRef<class FStressTester, ESPMode::ThreadSafe> FStressTester::Get() +{ + CheckInGameThread(); + if (!SharedInstance.IsValid()) + { + SharedInstance = TSharedPtr<class FStressTester, ESPMode::ThreadSafe>(new FStressTester()); + check(SharedInstance.IsValid()); + } + return SharedInstance.ToSharedRef(); +} + +FStressTester::FStressTester() + : Mode(STM_None) + , CPUSpinOffInSeconds(0.011 / 3.) // one third of the frame (default value) + , PDsTimeLimitInSeconds(10.) // 10 secs + , CPUsTimeLimitInSeconds(10.)// 10 secs + , GPUsTimeLimitInSeconds(10.)// 10 secs + , GPUIterationsMultiplier(0.) + , CPUStartTimeInSeconds(0.) + , GPUStartTimeInSeconds(0.) + , PDStartTimeInSeconds(0.) +{ + +} + +// multiple masks could be set, see EStressTestMode +void FStressTester::SetStressMode(uint32 InStressMask) +{ + check((InStressMask & (~STM__All)) == 0); + Mode = InStressMask; + + for (uint32 m = 1; m < STM__All; m <<= 1) + { + if (InStressMask & m) + { + switch (m) + { + case STM_EyeBufferRealloc: UE_LOG(LogHMD, Log, TEXT("PD of EyeBuffer stress test is started")); break; + case STM_CPUSpin: UE_LOG(LogHMD, Log, TEXT("CPU stress test is started")); break; + case STM_GPU: UE_LOG(LogHMD, Log, TEXT("GPU stress test is started")); break; + } + } + } +} + +void FStressTester::DoTickCPU_GameThread(FOculusXRHMD* pPlugin) +{ + CheckInGameThread(); + + if (Mode & STM_EyeBufferRealloc) + { + // Change PixelDensity every frame within MinPixelDensity..MaxPixelDensity range + if (PDStartTimeInSeconds == 0.) + { + PDStartTimeInSeconds = FPlatformTime::Seconds(); + } + else + { + const double Now = FPlatformTime::Seconds(); + if (Now - PDStartTimeInSeconds >= PDsTimeLimitInSeconds) + { + PDStartTimeInSeconds = 0.; + Mode &= ~STM_EyeBufferRealloc; + UE_LOG(LogHMD, Log, TEXT("PD of EyeBuffer stress test is finished")); + } + } + + const int divisor = int((MaxPixelDensity - MinPixelDensity)*10.f); + float NewPD = float(uint64(FPlatformTime::Seconds()*1000) % divisor) / 10.f + MinPixelDensity; + + pPlugin->SetPixelDensity(NewPD); + } + + if (Mode & STM_CPUSpin) + { + // Simulate heavy CPU load within specified time limits + + if (CPUStartTimeInSeconds == 0.) + { + CPUStartTimeInSeconds = FPlatformTime::Seconds(); + } + else + { + const double Now = FPlatformTime::Seconds(); + if (Now - CPUStartTimeInSeconds >= CPUsTimeLimitInSeconds) + { + CPUStartTimeInSeconds = 0.; + Mode &= ~STM_CPUSpin; + UE_LOG(LogHMD, Log, TEXT("CPU stress test is finished")); + } + } + + const double StartSeconds = FPlatformTime::Seconds(); + int i, num = 1, primes = 0; + + bool bFinish = false; + while (!bFinish) + { + i = 2; + while (i <= num) + { + if (num % i == 0) + { + break; + } + i++; + const double NowSeconds = FPlatformTime::Seconds(); + if (NowSeconds - StartSeconds >= CPUSpinOffInSeconds) + { + bFinish = true; + } + } + if (i == num) + { + ++primes; + } + + ++num; + } + } + + if (Mode & STM_GPU) + { + // Simulate heavy CPU load within specified time limits + + if (GPUStartTimeInSeconds == 0.) + { + GPUStartTimeInSeconds = FPlatformTime::Seconds(); + } + else + { + const double Now = FPlatformTime::Seconds(); + if (Now - GPUStartTimeInSeconds >= GPUsTimeLimitInSeconds) + { + GPUStartTimeInSeconds = 0.; + Mode &= ~STM_GPU; + UE_LOG(LogHMD, Log, TEXT("GPU stress test is finished")); + } + } + } +} + + +//------------------------------------------------------------------------------------------------- +// Console commands for managing the stress tester: +//------------------------------------------------------------------------------------------------- + +static void StressGPUCmdHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) +{ + auto StressTester = FStressTester::Get(); + StressTester->SetStressMode(FStressTester::STM_GPU | StressTester->GetStressMode()); + if (Args.Num() > 0) + { + const int GpuMult = FCString::Atoi(*Args[0]); + StressTester->SetGPULoadMultiplier(GpuMult); + } + if (Args.Num() > 1) + { + const float GpuTimeLimit = FCString::Atof(*Args[1]); + StressTester->SetGPUsTimeLimitInSeconds(GpuTimeLimit); + } +} + +static FAutoConsoleCommand CStressGPUCmd( + TEXT("vr.oculus.Stress.GPU"), + *NSLOCTEXT("OculusRift", "CCommandText_StressGPU", "Initiates a GPU stress test.\n Usage: vr.oculus.Stress.GPU [LoadMultiplier [TimeLimit]]").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(StressGPUCmdHandler)); + +static void StressCPUCmdHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) +{ + auto StressTester = FStressTester::Get(); + StressTester->SetStressMode(FStressTester::STM_CPUSpin | StressTester->GetStressMode()); + if (Args.Num() > 0) + { + const float CpuLimit = FCString::Atof(*Args[0]); + StressTester->SetCPUSpinOffPerFrameInSeconds(CpuLimit); + } + if (Args.Num() > 1) + { + const float CpuTimeLimit = FCString::Atof(*Args[1]); + StressTester->SetCPUsTimeLimitInSeconds(CpuTimeLimit); + } +} + +static FAutoConsoleCommand CStressCPUCmd( + TEXT("vr.oculus.Stress.CPU"), + *NSLOCTEXT("OculusRift", "CCommandText_StressCPU", "Initiates a CPU stress test.\n Usage: vr.oculus.Stress.CPU [PerFrameTime [TotalTimeLimit]]").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(StressCPUCmdHandler)); + +static void StressPDCmdHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) +{ + auto StressTester = FStressTester::Get(); + StressTester->SetStressMode(FStressTester::STM_EyeBufferRealloc | StressTester->GetStressMode()); + if (Args.Num() > 0) + { + const float TimeLimit = FCString::Atof(*Args[0]); + StressTester->SetPDsTimeLimitInSeconds(TimeLimit); + } +} + +static FAutoConsoleCommand CStressPDCmd( + TEXT("vr.oculus.Stress.PD"), + *NSLOCTEXT("OculusRift", "CCommandText_StressPD", "Initiates a pixel density stress test wher pixel density is changed every frame for TotalTimeLimit seconds.\n Usage: vr.oculus.Stress.PD [TotalTimeLimit]").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(StressPDCmdHandler)); + +static void StressResetCmdHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) +{ + auto StressTester = FStressTester::Get(); + StressTester->SetStressMode(0); +} + +static FAutoConsoleCommand CStressResetCmd( + TEXT("vr.oculus.Stress.Reset"), + *NSLOCTEXT("OculusRift", "CCommandText_StressReset", "Resets the stress tester and stops all currently running stress tests.\n Usage: vr.oculus.Stress.Reset").ToString(), + FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic(StressResetCmdHandler)); + + +} // namespace OculusXRHMD + +#endif // #if OCULUS_STRESS_TESTS_ENABLED diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_StressTester.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_StressTester.h new file mode 100644 index 0000000000000000000000000000000000000000..826d4b5c75505b11c62430d3caabc9838a0d90a6 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_StressTester.h @@ -0,0 +1,91 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" + +#define OCULUS_STRESS_TESTS_ENABLED (OCULUS_HMD_SUPPORTED_PLATFORMS && !UE_BUILD_SHIPPING && !PLATFORM_ANDROID) + +#if OCULUS_STRESS_TESTS_ENABLED + +namespace OculusXRHMD +{ + +//------------------------------------------------------------------------------------------------- +// FStressTester +//------------------------------------------------------------------------------------------------- + +class FStressTester +{ + +public: + const float MinPixelDensity = 0.4f; + const float MaxPixelDensity = 2.0f; + + enum EStressTestMode + { + STM_None, + STM_EyeBufferRealloc = 0x01, + STM_CPUSpin = 0x02, + STM_GPU = 0x04, + + STM__All = ((STM_GPU<<1)-1) + }; + + // multiple masks could be set, see EStressTestMode + void SetStressMode(uint32 InStressMask); + uint32 GetStressMode() const { return Mode; } + + // sets limits for CPUSpin mode, per frame + void SetCPUSpinOffPerFrameInSeconds(double InCPUSpinOffInSeconds) { CPUSpinOffInSeconds = InCPUSpinOffInSeconds; } + + // set GPU load multiplier + // if IterationsMultiplier is 0 then the multiplier will be randomly changed in 1..20 range. + // the bigger the multiplier the longer it takes GPU to draw the quad. + void SetGPULoadMultiplier(int IterationsMultiplier) { GPUIterationsMultiplier = IterationsMultiplier; } + + // sets time limit for STM_EyeBufferRealloc mode; 0 - unlimited + void SetPDsTimeLimitInSeconds(double InSeconds) { PDsTimeLimitInSeconds = InSeconds; } + + // sets time limit for STM_CPUSpin mode; 0 - unlimited + void SetCPUsTimeLimitInSeconds(double InSeconds) { CPUsTimeLimitInSeconds = InSeconds; } + + // sets time limit for STM_GPU mode; 0 - unlimited + void SetGPUsTimeLimitInSeconds(double InSeconds) { GPUsTimeLimitInSeconds = InSeconds; } + + static TSharedRef<class FStressTester, ESPMode::ThreadSafe> Get(); + + static void TickCPU_GameThread(class FOculusXRHMD* pPlugin) + { + CheckInGameThread(); + + if (SharedInstance.IsValid()) + { + SharedInstance->DoTickCPU_GameThread(pPlugin); + } + } + +protected: + void DoTickCPU_GameThread(class FOculusXRHMD* pPlugin); + + FStressTester(); + + uint32 Mode; // bit mask, see EStressTestMode + double CPUSpinOffInSeconds; // limit of additional CPU load per frame, STM_CPUSpin + double PDsTimeLimitInSeconds; // time limit for STM_EyeBufferRealloc mode; 0 - unlimited + double CPUsTimeLimitInSeconds; // time limit for STM_CPUSpin mode; 0 - unlimited + double GPUsTimeLimitInSeconds; // time limit for STM_GPU mode; 0 - unlimited + + // the higher multiplier the longer it takes GPU to draw + int GPUIterationsMultiplier; // if 0 - then it is dynamically changed. + + double CPUStartTimeInSeconds; + double GPUStartTimeInSeconds; + double PDStartTimeInSeconds; + + static TSharedPtr<class FStressTester, ESPMode::ThreadSafe> SharedInstance; +}; + + +} // namespace OculusXRHMD + +#endif // #if OCULUS_STRESS_TESTS_ENABLED diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_VulkanExtensions.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_VulkanExtensions.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9f9de24db6ff0d4a7ceed02217340247a6433311 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_VulkanExtensions.cpp @@ -0,0 +1,91 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHMD_VulkanExtensions.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS +#include "OculusXRHMDPrivateRHI.h" +#include "OculusXRHMDModule.h" + +namespace OculusXRHMD +{ + + +//------------------------------------------------------------------------------------------------- +// FVulkanExtensions +//------------------------------------------------------------------------------------------------- + +bool FVulkanExtensions::GetVulkanInstanceExtensionsRequired(TArray<const ANSICHAR*>& Out) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN + //TArray<VkExtensionProperties> Properties = GetIVulkanDynamicRHI()->RHIGetAllInstanceExtensions(); + + TArray<const char*> Extensions; + { + int32 ExtensionCount = 0; + FOculusXRHMDModule::GetPluginWrapper().GetInstanceExtensionsVk(nullptr, &ExtensionCount); + Extensions.SetNum(ExtensionCount); + FOculusXRHMDModule::GetPluginWrapper().GetInstanceExtensionsVk(Extensions.GetData(), &ExtensionCount); + } + + // int32 ExtensionsFound = 0; + for (int32 ExtensionIndex = 0; ExtensionIndex < Extensions.Num(); ExtensionIndex++) + { + // for (int32 PropertyIndex = 0; PropertyIndex < Properties.Num(); PropertyIndex++) + { + // const char* PropertyExtensionName = Properties[PropertyIndex].extensionName; + + // if (!FCStringAnsi::Strcmp(PropertyExtensionName, Extensions[ExtensionIndex])) + { + Out.Add(Extensions[ExtensionIndex]); + // ExtensionsFound++; + // break; + } + } + } + return true; + +// return ExtensionsFound == Extensions.Num(); +#endif + return true; +} + + +bool FVulkanExtensions::GetVulkanDeviceExtensionsRequired(struct VkPhysicalDevice_T *pPhysicalDevice, TArray<const ANSICHAR*>& Out) +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS_VULKAN + //TArray<VkExtensionProperties> Properties = GetIVulkanDynamicRHI()->RHIGetAllDeviceExtensions((VkPhysicalDevice)pPhysicalDevice); + + TArray<const char*> Extensions; + { + int32 ExtensionCount = 0; + FOculusXRHMDModule::GetPluginWrapper().GetDeviceExtensionsVk(nullptr, &ExtensionCount); + Extensions.SetNum(ExtensionCount); + FOculusXRHMDModule::GetPluginWrapper().GetDeviceExtensionsVk(Extensions.GetData(), &ExtensionCount); + } + +// int32 ExtensionsFound = 0; + for (int32 ExtensionIndex = 0; ExtensionIndex < Extensions.Num(); ExtensionIndex++) + { + // for (int32 PropertyIndex = 0; PropertyIndex < Properties.Num(); PropertyIndex++) + { + // const char* PropertyExtensionName = Properties[PropertyIndex].extensionName; + + // if (!FCStringAnsi::Strcmp(PropertyExtensionName, Extensions[ExtensionIndex])) + { + Out.Add(Extensions[ExtensionIndex]); + // ExtensionsFound++; + // break; + } + } + } + return true; + + // return ExtensionsFound == Extensions.Num(); +#endif + return true; +} + + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_VulkanExtensions.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_VulkanExtensions.h new file mode 100644 index 0000000000000000000000000000000000000000..6da024a21163135c0d5fdc5048dda166249f4e57 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRHMD_VulkanExtensions.h @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" +#include "IHeadMountedDisplayVulkanExtensions.h" + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + +namespace OculusXRHMD +{ + + +//------------------------------------------------------------------------------------------------- +// FVulkanExtensions +//------------------------------------------------------------------------------------------------- + +class FVulkanExtensions : public IHeadMountedDisplayVulkanExtensions, public TSharedFromThis<FVulkanExtensions, ESPMode::ThreadSafe> +{ +public: + FVulkanExtensions() {} + virtual ~FVulkanExtensions() {} + + // IHeadMountedDisplayVulkanExtensions + virtual bool GetVulkanInstanceExtensionsRequired(TArray<const ANSICHAR*>& Out) override; + virtual bool GetVulkanDeviceExtensionsRequired(struct VkPhysicalDevice_T *pPhysicalDevice, TArray<const ANSICHAR*>& Out) override; +}; + +} // namespace OculusXRHMD + +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPassthroughLayerComponent.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPassthroughLayerComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..fe450fe93baa90170c64f4c2fff20682e1df4afa --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPassthroughLayerComponent.cpp @@ -0,0 +1,463 @@ +// Copyright 1998-2020 Epic Games, Inc. All Rights Reserved. + +#include "OculusXRPassthroughLayerComponent.h" + +#include "Engine/StaticMesh.h" +#include "Components/StaticMeshComponent.h" +#include "OculusXRHMD.h" +#include "OculusXRPassthroughLayerShapes.h" +#include "Curves/CurveLinearColor.h" + +DEFINE_LOG_CATEGORY(LogOculusPassthrough); + +void UOculusXRStereoLayerShapeReconstructed::ApplyShape(IStereoLayers::FLayerDesc& LayerDesc) +{ + const FEdgeStyleParameters EdgeStyleParameters( + bEnableEdgeColor, + bEnableColorMap, + TextureOpacityFactor, + Brightness, + Contrast, + Posterize, + Saturation, + EdgeColor, + ColorScale, + ColorOffset, + ColorMapType, + GetColorArray(bUseColorMapCurve, ColorMapCurve)); + LayerDesc.SetShape<FReconstructedLayer>(EdgeStyleParameters, LayerOrder); +} + +void UOculusXRStereoLayerShapeUserDefined::ApplyShape(IStereoLayers::FLayerDesc& LayerDesc) +{ + const FEdgeStyleParameters EdgeStyleParameters( + bEnableEdgeColor, + bEnableColorMap, + TextureOpacityFactor, + Brightness, + Contrast, + Posterize, + Saturation, + EdgeColor, + ColorScale, + ColorOffset, + ColorMapType, + GetColorArray(bUseColorMapCurve, ColorMapCurve)); + LayerDesc.SetShape<FUserDefinedLayer>(UserGeometryList,EdgeStyleParameters, LayerOrder); +} + +void UOculusXRStereoLayerShapeUserDefined::AddGeometry(const FString& MeshName, OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FTransform Transform, bool bUpdateTransform) +{ + FUserDefinedGeometryDesc UserDefinedGeometryDesc( + MeshName, + PassthroughMesh, + Transform, + bUpdateTransform); + + UserGeometryList.Add(UserDefinedGeometryDesc); +} + +void UOculusXRStereoLayerShapeUserDefined::RemoveGeometry(const FString& MeshName) +{ + UserGeometryList.RemoveAll([MeshName](const FUserDefinedGeometryDesc& Desc) { + return Desc.MeshName == MeshName; + }); +} + +UOculusXRPassthroughLayerComponent::UOculusXRPassthroughLayerComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + + +void UOculusXRPassthroughLayerComponent::DestroyComponent(bool bPromoteChildren) +{ + Super::DestroyComponent(bPromoteChildren); +#ifdef WITH_OCULUS_BRANCH + IStereoLayers* StereoLayers; + if (LayerId && GEngine->StereoRenderingDevice.IsValid() && (StereoLayers = GEngine->StereoRenderingDevice->GetStereoLayers()) != nullptr) + { + StereoLayers->DestroyLayer(LayerId); + LayerId = 0; + } +#endif +} + +void UOculusXRPassthroughLayerComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) +{ +#ifndef WITH_OCULUS_BRANCH + if(Texture == nullptr && !LayerRequiresTexture()) + { + // UStereoLayerComponent hides components without textures + Texture = GEngine->DefaultTexture; + } +#endif + Super::TickComponent( DeltaTime, TickType, ThisTickFunction ); + UpdatePassthroughObjects(); +} + +void UOculusXRPassthroughLayerComponent::UpdatePassthroughObjects() +{ + UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape); + if (UserShape) + { + bool bDirty = false; + for (FUserDefinedGeometryDesc& Entry : UserShape->GetUserGeometryList()) + { + if (Entry.bUpdateTransform) + { + AStaticMeshActor** StaticMeshActor = PassthroughActorMap.Find(Entry.MeshName); + if (StaticMeshActor) + { + UStaticMeshComponent* StaticMeshComponent = (*StaticMeshActor)->GetStaticMeshComponent(); + if (StaticMeshComponent) + { + Entry.Transform = StaticMeshComponent->GetComponentTransform(); + bDirty = true; + } + } + } + } + if (bDirty) { + MarkStereoLayerDirty(); + } + } +} + + +OculusXRHMD::FOculusPassthroughMeshRef UOculusXRPassthroughLayerComponent::CreatePassthroughMesh(UStaticMesh* Mesh) +{ + if (!Mesh || !Mesh->GetRenderData()) + { + UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh has no Renderdata")); + return nullptr; + } + + if(Mesh->GetNumLODs() == 0) + { + UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh has no LODs")); + return nullptr; + } + + if (!Mesh->bAllowCPUAccess) + { + UE_LOG(LogOculusPassthrough, Error, TEXT("Passthrough Static Mesh Requires CPU Access")); + return nullptr; + } + + const int32 LODIndex = 0; + FStaticMeshLODResources& LOD = Mesh->GetRenderData()->LODResources[LODIndex]; + + TArray<int32> Triangles; + const int32 NumIndices = LOD.IndexBuffer.GetNumIndices(); + for (int32 i = 0 ; i < NumIndices ; ++i) + { + Triangles.Add(LOD.IndexBuffer.GetIndex(i)); + } + + TArray<FVector> Vertices; + const int32 NumVertices = LOD.VertexBuffers.PositionVertexBuffer.GetNumVertices(); + for (int32 i = 0 ; i < NumVertices ; ++i) + { + Vertices.Add((FVector)LOD.VertexBuffers.PositionVertexBuffer.VertexPosition(i)); + } + + OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = new OculusXRHMD::FOculusPassthroughMesh(Vertices, Triangles); + return PassthroughMesh; +} + +void UOculusXRPassthroughLayerComponent::AddSurfaceGeometry(AStaticMeshActor* StaticMeshActor, bool updateTransform ) +{ + if (StaticMeshActor) + { + UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape); + if (UserShape) + { + UStaticMeshComponent* StaticMeshComponent = StaticMeshActor->GetStaticMeshComponent(); + if (StaticMeshComponent) + { + UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh(); + if (StaticMesh) + { + OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh = CreatePassthroughMesh(StaticMesh); + if (PassthroughMesh) + { + const FString MeshName = StaticMeshActor->GetFullName(); + const FTransform Transform = StaticMeshComponent->GetComponentTransform(); + UserShape->AddGeometry(MeshName, PassthroughMesh, Transform, updateTransform); + } + } + } + + PassthroughActorMap.Add(StaticMeshActor->GetFullName(),StaticMeshActor); + MarkStereoLayerDirty(); + } + } +} + +void UOculusXRPassthroughLayerComponent::RemoveSurfaceGeometry(AStaticMeshActor* StaticMeshActor ) +{ + if (StaticMeshActor) + { + UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape); + if (UserShape) + { + UStaticMeshComponent *StaticMeshComponent = StaticMeshActor->GetStaticMeshComponent(); + if (StaticMeshComponent) + { + UStaticMesh* StaticMesh = StaticMeshComponent->GetStaticMesh(); + if (StaticMesh) + { + const FString MeshName = StaticMeshActor->GetFullName(); + UserShape->RemoveGeometry(MeshName); + } + } + PassthroughActorMap.Remove(StaticMeshActor->GetFullName()); + } + } + + MarkStereoLayerDirty(); +} + +bool UOculusXRPassthroughLayerComponent::IsSurfaceGeometry(AStaticMeshActor* StaticMeshActor) const +{ + if (StaticMeshActor) + { + UOculusXRStereoLayerShapeUserDefined* UserShape = Cast<UOculusXRStereoLayerShapeUserDefined>(Shape); + if (UserShape) + { + return PassthroughActorMap.Contains(StaticMeshActor->GetFullName()); + } + } + + return false; +} + +void UOculusXRPassthroughLayerComponent::MarkPassthroughStyleForUpdate() +{ + bPassthroughStyleNeedsUpdate = true; +} + +bool UOculusXRPassthroughLayerComponent::LayerRequiresTexture() +{ + const bool bIsPassthroughShape = Shape && (Shape->IsA<UOculusXRStereoLayerShapeReconstructed>() || + Shape->IsA<UOculusXRStereoLayerShapeUserDefined>()); + return !bIsPassthroughShape; +} + +void UOculusXRPassthroughLayerBase::SetTextureOpacity(float InOpacity) +{ + if(TextureOpacityFactor == InOpacity) + { + return; + } + + TextureOpacityFactor = InOpacity; + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::EnableEdgeColor(bool bInEnableEdgeColor) +{ + if (bEnableEdgeColor == bInEnableEdgeColor) + { + return; + } + bEnableEdgeColor = bInEnableEdgeColor; + MarkStereoLayerDirty(); +} + + +void UOculusXRPassthroughLayerBase::EnableColorMap(bool bInEnableColorMap) +{ + if (bEnableColorMap == bInEnableColorMap) + { + return; + } + bEnableColorMap = bInEnableColorMap; + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::SetEdgeRenderingColor(FLinearColor InEdgeColor) +{ + if(EdgeColor == InEdgeColor) + { + return; + } + EdgeColor = InEdgeColor; + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::EnableColorMapCurve(bool bInEnableColorMapCurve) +{ + if (bUseColorMapCurve == bInEnableColorMapCurve) + { + return; + } + bUseColorMapCurve = bInEnableColorMapCurve; + ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve); + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::SetColorMapCurve(UCurveLinearColor* InColorMapCurve) +{ + if(ColorMapCurve == InColorMapCurve) + { + return; + } + ColorMapCurve = InColorMapCurve; + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::SetColorMapType(EOculusXRColorMapType InColorMapType) +{ + if(ColorMapType == InColorMapType) + { + return; + } + ColorMapType = InColorMapType; + ColorArray = GenerateColorArray(bUseColorMapCurve, ColorMapCurve); + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::SetColorArray(const TArray<FLinearColor>& InColorArray) +{ + if (InColorArray.Num() == 0) + { + return; + } + + if (ColorMapType != ColorMapType_GrayscaleToColor) { + UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorArray is ignored for color map types other than Grayscale to Color.")); + return; + } + + if (bUseColorMapCurve) + { + UE_LOG(LogOculusPassthrough, Warning, TEXT("UseColorMapCurve is enabled on the layer. Automatic disable and use the Array for color lookup")); + } + bUseColorMapCurve = false; + + ColorArray = InColorArray; + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::ClearColorMap() +{ + ColorArray.Empty(); +} + +void UOculusXRPassthroughLayerBase::SetColorMapControls(float InContrast, float InBrightness, float InPosterize) +{ + if (ColorMapType != ColorMapType_Grayscale && ColorMapType != ColorMapType_GrayscaleToColor) { + UE_LOG(LogOculusPassthrough, Warning, TEXT("SetColorMapControls is ignored for color map types other than Grayscale and Grayscale to color.")); + return; + } + Contrast = FMath::Clamp(InContrast, -1.0f, 1.0f); + Brightness = FMath::Clamp(InBrightness, -1.0f, 1.0f); + Posterize = FMath::Clamp(InPosterize, 0.0f, 1.0f); + + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::SetBrightnessContrastSaturation(float InContrast, float InBrightness, float InSaturation) +{ + if (ColorMapType != ColorMapType_ColorAdjustment) { + UE_LOG(LogOculusPassthrough, Warning, TEXT("SetBrightnessContrastSaturation is ignored for color map types other than Color Adjustment.")); + return; + } + Contrast = FMath::Clamp(InContrast, -1.0f, 1.0f); + Brightness = FMath::Clamp(InBrightness, -1.0f, 1.0f); + Saturation = FMath::Clamp(InSaturation, -1.0f, 1.0f); + + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::SetColorScaleAndOffset(FLinearColor InColorScale, FLinearColor InColorOffset) +{ + if (ColorScale == InColorScale && ColorOffset == InColorOffset) + { + return; + } + ColorScale = InColorScale; + ColorOffset = InColorOffset; + MarkStereoLayerDirty(); +} + +void UOculusXRPassthroughLayerBase::SetLayerPlacement(EOculusXRPassthroughLayerOrder InLayerOrder) +{ + if (LayerOrder == InLayerOrder) + { + UE_LOG(LogOculusPassthrough, Warning, TEXT("Same layer order as before, no change needed")); + return; + } + + LayerOrder = InLayerOrder; + this->MarkStereoLayerDirty(); +} + +TArray<FLinearColor> UOculusXRPassthroughLayerBase::GenerateColorArrayFromColorCurve(const UCurveLinearColor* InColorMapCurve) const +{ + if (InColorMapCurve == nullptr) + { + return TArray<FLinearColor>(); + } + + TArray<FLinearColor> NewColorArray; + constexpr uint32 TotalEntries = 256; + NewColorArray.Empty(); + NewColorArray.SetNum(TotalEntries); + + for (int32 Index = 0; Index < TotalEntries; ++Index) + { + const float Alpha = ((float)Index / TotalEntries); + NewColorArray[Index] = InColorMapCurve->GetLinearColorValue(Alpha); + } + return NewColorArray; +} + +TArray<FLinearColor> UOculusXRPassthroughLayerBase::GetOrGenerateNeutralColorArray() +{ + if (NeutralColorArray.Num() == 0) { + const uint32 TotalEntries = 256; + NeutralColorArray.SetNum(TotalEntries); + + for (int32 Index = 0; Index < TotalEntries; ++Index) + { + NeutralColorArray[Index] = FLinearColor((float)Index / TotalEntries, (float)Index / TotalEntries, (float)Index / TotalEntries); + } + } + + return NeutralColorArray; +} + +TArray<FLinearColor> UOculusXRPassthroughLayerBase::GenerateColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve) +{ + TArray<FLinearColor> NewColorArray; + if (bInUseColorMapCurve) + { + NewColorArray = GenerateColorArrayFromColorCurve(InColorMapCurve); + } + + // Check for existing Array, otherwise generate a neutral one + if (NewColorArray.Num() == 0) + { + NewColorArray = GetOrGenerateNeutralColorArray(); + } + + return NewColorArray; +} + +TArray<FLinearColor> UOculusXRPassthroughLayerBase::GetColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve) +{ + if (ColorArray.Num() == 0) { + if (bInUseColorMapCurve) { + return GenerateColorArray(bInUseColorMapCurve, InColorMapCurve); + } + return GetOrGenerateNeutralColorArray(); + } + + return ColorArray; +} + + + diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPassthroughLayerShapes.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPassthroughLayerShapes.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f51e3e702c6a22a3705a40a6aeef6b24526cfd41 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPassthroughLayerShapes.cpp @@ -0,0 +1,85 @@ +#include "OculusXRPassthroughLayerShapes.h" +#include "Curves/CurveLinearColor.h" + +const FName FReconstructedLayer::ShapeName = FName("ReconstructedLayer"); +const FName FUserDefinedLayer::ShapeName = FName("UserDefinedLayer"); + + +TArray<uint8> FEdgeStyleParameters::GenerateColorMapData(EOculusXRColorMapType InColorMapType, const TArray<FLinearColor>& InColorMapGradient) +{ + switch (InColorMapType) { + case ColorMapType_GrayscaleToColor: { + TArray<uint8> NewColorMapData = GenerateMonoBrightnessContrastPosterizeMap(); + return GenerateMonoToRGBA(InColorMapGradient, NewColorMapData); + } + case ColorMapType_Grayscale: + return GenerateMonoBrightnessContrastPosterizeMap(); + case ColorMapType_ColorAdjustment: + return GenerateBrightnessContrastSaturationColorMap(); + default: + return TArray<uint8>(); + } +} + +TArray<uint8> FEdgeStyleParameters::GenerateMonoToRGBA(const TArray<FLinearColor>& InColorMapGradient, const TArray<uint8>& InColorMapData) +{ + TArray<uint8> NewColorMapData; + FInterpCurveLinearColor InterpCurve; + const uint32 TotalEntries = 256; + + for (int32 Index = 0; Index < InColorMapGradient.Num(); ++Index) + { + InterpCurve.AddPoint(Index, (InColorMapGradient[Index] * ColorScale) + ColorOffset); + } + + NewColorMapData.SetNum(TotalEntries * sizeof(ovrpColorf)); + uint8* Dest = NewColorMapData.GetData(); + for (int32 Index = 0; Index < TotalEntries; ++Index) + { + const ovrpColorf Color = OculusXRHMD::ToOvrpColorf(InterpCurve.Eval(InColorMapData[Index])); + FMemory::Memcpy(Dest, &Color, sizeof(Color)); + Dest += sizeof(ovrpColorf); + } + return NewColorMapData; +} + +TArray<uint8> FEdgeStyleParameters::GenerateMonoBrightnessContrastPosterizeMap() +{ + TArray<uint8> NewColorMapData; + const int32 TotalEntries = 256; + NewColorMapData.SetNum(TotalEntries * sizeof(uint8)); + for (int32 Index = 0; Index < TotalEntries; ++Index) + { + float Alpha = ((float)Index / TotalEntries); + float ContrastFactor = Contrast + 1.0; + Alpha = (Alpha - 0.5) * ContrastFactor + 0.5 + Brightness; + + if (Posterize > 0.0f) + { + const float PosterizationBase = 50.0f; + float FinalPosterize = (FMath::Pow(PosterizationBase, Posterize) - 1.0) / (PosterizationBase - 1.0); + Alpha = FMath::RoundToFloat(Alpha / FinalPosterize) * FinalPosterize; + } + + NewColorMapData[Index] = (uint8)(FMath::Min(FMath::Max(Alpha, 0.0f), 1.0f) * 255.0f); + } + return NewColorMapData; +} + +TArray<uint8> FEdgeStyleParameters::GenerateBrightnessContrastSaturationColorMap() +{ + TArray<uint8> NewColorMapData; + NewColorMapData.SetNum(3 * sizeof(float)); + float newB = Brightness * 100.0f; + float newC = Contrast + 1.0f; + float newS = Saturation + 1.0f; + + uint8* Dest = NewColorMapData.GetData(); + FMemory::Memcpy(Dest, &newB, sizeof(float)); + Dest += sizeof(float); + FMemory::Memcpy(Dest, &newC, sizeof(float)); + Dest += sizeof(float); + FMemory::Memcpy(Dest, &newS, sizeof(float)); + + return NewColorMapData; +} diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPluginWrapper.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPluginWrapper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b2ff8a5825ec2cd0a452ee7d78f9d1c5d8205671 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPluginWrapper.cpp @@ -0,0 +1,440 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRPluginWrapper.h" +#include "OculusXRHMDModule.h" + +#if PLATFORM_ANDROID +#include <dlfcn.h> +#define MIN_SDK_VERSION 29 +#endif + +DEFINE_LOG_CATEGORY(LogOculusPluginWrapper); + +static void* LoadEntryPoint(void* handle, const char* EntryPointName); + +bool OculusPluginWrapper::InitializeOculusPluginWrapper(OculusPluginWrapper* wrapper) +{ + if (wrapper->IsInitialized()) + { + UE_LOG(LogOculusPluginWrapper, Warning, TEXT("wrapper already initialized")); + return true; + } + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + void* LibraryHandle = nullptr; + +#if PLATFORM_ANDROID + const bool VersionValid = FAndroidMisc::GetAndroidBuildVersion() >= MIN_SDK_VERSION; +#else + const bool VersionValid = true; +#endif + + if (VersionValid) + { + LibraryHandle = FOculusXRHMDModule::GetOVRPluginHandle(); + if (LibraryHandle == nullptr) + { + UE_LOG(LogOculusPluginWrapper, Warning, TEXT("GetOVRPluginHandle() returned NULL")); + return false; + } + } + else + { + return false; + } +#else + return false; +#endif + + + struct OculusEntryPoint + { + const char* EntryPointName; + void** EntryPointPtr; + }; + +#define OCULUS_BIND_ENTRY_POINT(Func) { "ovrp_"#Func, (void**)&wrapper->Func } + + OculusEntryPoint entryPointArray[] = + { + // OVR_Plugin.h + + OCULUS_BIND_ENTRY_POINT(PreInitialize5), + OCULUS_BIND_ENTRY_POINT(GetInitialized), + OCULUS_BIND_ENTRY_POINT(Initialize7), + OCULUS_BIND_ENTRY_POINT(Shutdown2), + OCULUS_BIND_ENTRY_POINT(GetVersion2), + OCULUS_BIND_ENTRY_POINT(GetNativeSDKVersion2), + OCULUS_BIND_ENTRY_POINT(GetNativeSDKPointer2), + OCULUS_BIND_ENTRY_POINT(GetDisplayAdapterId2), + OCULUS_BIND_ENTRY_POINT(GetAudioOutId2), + OCULUS_BIND_ENTRY_POINT(GetAudioOutDeviceId2), + OCULUS_BIND_ENTRY_POINT(GetAudioInId2), + OCULUS_BIND_ENTRY_POINT(GetAudioInDeviceId2), + OCULUS_BIND_ENTRY_POINT(GetInstanceExtensionsVk), + OCULUS_BIND_ENTRY_POINT(GetDeviceExtensionsVk), + OCULUS_BIND_ENTRY_POINT(SetupDistortionWindow3), + OCULUS_BIND_ENTRY_POINT(DestroyDistortionWindow2), + OCULUS_BIND_ENTRY_POINT(GetDominantHand), + OCULUS_BIND_ENTRY_POINT(SetRemoteHandedness), + OCULUS_BIND_ENTRY_POINT(SetColorScaleAndOffset), + OCULUS_BIND_ENTRY_POINT(SetupLayer), + OCULUS_BIND_ENTRY_POINT(SetupLayerDepth), + OCULUS_BIND_ENTRY_POINT(SetEyeFovPremultipliedAlphaMode), + OCULUS_BIND_ENTRY_POINT(GetEyeFovLayerId), + OCULUS_BIND_ENTRY_POINT(GetLayerTextureStageCount), + OCULUS_BIND_ENTRY_POINT(GetLayerTexture2), + OCULUS_BIND_ENTRY_POINT(GetLayerTextureFoveation), + OCULUS_BIND_ENTRY_POINT(GetLayerOcclusionMesh), + OCULUS_BIND_ENTRY_POINT(GetLayerAndroidSurfaceObject), + OCULUS_BIND_ENTRY_POINT(GetLayerTextureSpaceWarp), + OCULUS_BIND_ENTRY_POINT(CalculateEyeLayerDesc3), + OCULUS_BIND_ENTRY_POINT(DestroyLayer), + OCULUS_BIND_ENTRY_POINT(CalculateLayerDesc), + OCULUS_BIND_ENTRY_POINT(CalculateEyeLayerDesc2), + OCULUS_BIND_ENTRY_POINT(CalculateEyeViewportRect), + OCULUS_BIND_ENTRY_POINT(CalculateEyePreviewRect), + OCULUS_BIND_ENTRY_POINT(SetupMirrorTexture2), + OCULUS_BIND_ENTRY_POINT(DestroyMirrorTexture2), + OCULUS_BIND_ENTRY_POINT(GetAdaptiveGpuPerformanceScale2), + OCULUS_BIND_ENTRY_POINT(GetAppCpuStartToGpuEndTime2), + OCULUS_BIND_ENTRY_POINT(GetEyePixelsPerTanAngleAtCenter2), + OCULUS_BIND_ENTRY_POINT(GetHmdToEyeOffset2), + OCULUS_BIND_ENTRY_POINT(Update3), + OCULUS_BIND_ENTRY_POINT(WaitToBeginFrame), + OCULUS_BIND_ENTRY_POINT(BeginFrame4), + OCULUS_BIND_ENTRY_POINT(UpdateFoveation), + OCULUS_BIND_ENTRY_POINT(EndFrame4), + OCULUS_BIND_ENTRY_POINT(GetTrackingOrientationSupported2), + OCULUS_BIND_ENTRY_POINT(GetTrackingOrientationEnabled2), + OCULUS_BIND_ENTRY_POINT(SetTrackingOrientationEnabled2), + OCULUS_BIND_ENTRY_POINT(GetTrackingPositionSupported2), + OCULUS_BIND_ENTRY_POINT(GetTrackingPositionEnabled2), + OCULUS_BIND_ENTRY_POINT(SetTrackingPositionEnabled2), + OCULUS_BIND_ENTRY_POINT(GetTrackingIPDEnabled2), + OCULUS_BIND_ENTRY_POINT(SetTrackingIPDEnabled2), + OCULUS_BIND_ENTRY_POINT(GetTrackingCalibratedOrigin2), + OCULUS_BIND_ENTRY_POINT(SetTrackingCalibratedOrigin2), + OCULUS_BIND_ENTRY_POINT(GetTrackingOriginType2), + OCULUS_BIND_ENTRY_POINT(SetTrackingOriginType2), + OCULUS_BIND_ENTRY_POINT(RecenterTrackingOrigin2), + OCULUS_BIND_ENTRY_POINT(GetNodePresent2), + OCULUS_BIND_ENTRY_POINT(GetNodeOrientationTracked2), + OCULUS_BIND_ENTRY_POINT(GetNodeOrientationValid), + OCULUS_BIND_ENTRY_POINT(GetNodePositionTracked2), + OCULUS_BIND_ENTRY_POINT(GetNodePositionValid), + OCULUS_BIND_ENTRY_POINT(SetNodePositionTracked2), + OCULUS_BIND_ENTRY_POINT(GetNodePoseState3), + OCULUS_BIND_ENTRY_POINT(GetNodePoseStateRaw), + OCULUS_BIND_ENTRY_POINT(GetNodeFrustum2), + OCULUS_BIND_ENTRY_POINT(SetHeadPoseModifier), + OCULUS_BIND_ENTRY_POINT(GetHeadPoseModifier), + OCULUS_BIND_ENTRY_POINT(GetControllerState4), + OCULUS_BIND_ENTRY_POINT(GetControllerState5), + OCULUS_BIND_ENTRY_POINT(GetActiveController2), + OCULUS_BIND_ENTRY_POINT(GetConnectedControllers2), + OCULUS_BIND_ENTRY_POINT(SetControllerVibration2), + OCULUS_BIND_ENTRY_POINT(SetControllerLocalizedVibration), + OCULUS_BIND_ENTRY_POINT(SetControllerHapticsAmplitudeEnvelope), + OCULUS_BIND_ENTRY_POINT(SetControllerHapticsPcm), + OCULUS_BIND_ENTRY_POINT(GetControllerHapticsDesc2), + OCULUS_BIND_ENTRY_POINT(GetControllerHapticsState2), + OCULUS_BIND_ENTRY_POINT(GetControllerSampleRateHz), + OCULUS_BIND_ENTRY_POINT(SetControllerHaptics2), + OCULUS_BIND_ENTRY_POINT(SetSuggestedCpuPerformanceLevel), + OCULUS_BIND_ENTRY_POINT(GetSuggestedCpuPerformanceLevel), + OCULUS_BIND_ENTRY_POINT(SetSuggestedGpuPerformanceLevel), + OCULUS_BIND_ENTRY_POINT(GetSuggestedGpuPerformanceLevel), + OCULUS_BIND_ENTRY_POINT(GetAppCPUPriority2), + OCULUS_BIND_ENTRY_POINT(SetAppCPUPriority2), + OCULUS_BIND_ENTRY_POINT(GetSystemPowerSavingMode2), + OCULUS_BIND_ENTRY_POINT(GetSystemDisplayFrequency2), + OCULUS_BIND_ENTRY_POINT(GetSystemDisplayAvailableFrequencies), + OCULUS_BIND_ENTRY_POINT(SetSystemDisplayFrequency), + OCULUS_BIND_ENTRY_POINT(GetSystemVSyncCount2), + OCULUS_BIND_ENTRY_POINT(SetSystemVSyncCount2), + OCULUS_BIND_ENTRY_POINT(GetSystemProductName2), + OCULUS_BIND_ENTRY_POINT(GetSystemRegion2), + OCULUS_BIND_ENTRY_POINT(ShowSystemUI2), + OCULUS_BIND_ENTRY_POINT(GetAppHasVrFocus2), + OCULUS_BIND_ENTRY_POINT(GetAppHasInputFocus), + OCULUS_BIND_ENTRY_POINT(GetAppHasSystemOverlayPresent), + OCULUS_BIND_ENTRY_POINT(GetAppShouldQuit2), + OCULUS_BIND_ENTRY_POINT(GetAppShouldRecenter2), + OCULUS_BIND_ENTRY_POINT(GetAppShouldRecreateDistortionWindow2), + OCULUS_BIND_ENTRY_POINT(GetAppLatencyTimings2), + OCULUS_BIND_ENTRY_POINT(SetAppEngineInfo2), + OCULUS_BIND_ENTRY_POINT(GetUserPresent2), + OCULUS_BIND_ENTRY_POINT(GetUserIPD2), + OCULUS_BIND_ENTRY_POINT(SetUserIPD2), + OCULUS_BIND_ENTRY_POINT(GetUserEyeHeight2), + OCULUS_BIND_ENTRY_POINT(SetUserEyeHeight2), + OCULUS_BIND_ENTRY_POINT(GetUserNeckEyeDistance2), + OCULUS_BIND_ENTRY_POINT(SetUserNeckEyeDistance2), + OCULUS_BIND_ENTRY_POINT(SetupDisplayObjects2), + OCULUS_BIND_ENTRY_POINT(GetSystemMultiViewSupported2), + OCULUS_BIND_ENTRY_POINT(GetEyeTextureArraySupported2), + OCULUS_BIND_ENTRY_POINT(GetBoundaryConfigured2), + OCULUS_BIND_ENTRY_POINT(GetDepthCompositingSupported), + OCULUS_BIND_ENTRY_POINT(TestBoundaryNode2), + OCULUS_BIND_ENTRY_POINT(TestBoundaryPoint2), + OCULUS_BIND_ENTRY_POINT(GetBoundaryGeometry3), + OCULUS_BIND_ENTRY_POINT(GetBoundaryDimensions2), + OCULUS_BIND_ENTRY_POINT(GetBoundaryVisible2), + OCULUS_BIND_ENTRY_POINT(SetBoundaryVisible2), + OCULUS_BIND_ENTRY_POINT(GetSystemHeadsetType2), + OCULUS_BIND_ENTRY_POINT(GetAppPerfStats2), + OCULUS_BIND_ENTRY_POINT(ResetAppPerfStats2), + OCULUS_BIND_ENTRY_POINT(GetAppFramerate2), + OCULUS_BIND_ENTRY_POINT(IsPerfMetricsSupported), + OCULUS_BIND_ENTRY_POINT(GetPerfMetricsFloat), + OCULUS_BIND_ENTRY_POINT(GetPerfMetricsInt), + OCULUS_BIND_ENTRY_POINT(SetHandNodePoseStateLatency), + OCULUS_BIND_ENTRY_POINT(GetHandNodePoseStateLatency), + OCULUS_BIND_ENTRY_POINT(GetSystemRecommendedMSAALevel2), + OCULUS_BIND_ENTRY_POINT(SetInhibitSystemUX2), + OCULUS_BIND_ENTRY_POINT(GetTiledMultiResSupported), + OCULUS_BIND_ENTRY_POINT(GetTiledMultiResLevel), + OCULUS_BIND_ENTRY_POINT(SetTiledMultiResLevel), + OCULUS_BIND_ENTRY_POINT(GetTiledMultiResDynamic), + OCULUS_BIND_ENTRY_POINT(SetTiledMultiResDynamic), + OCULUS_BIND_ENTRY_POINT(GetFoveationEyeTrackedSupported), + OCULUS_BIND_ENTRY_POINT(GetFoveationEyeTracked), + OCULUS_BIND_ENTRY_POINT(SetFoveationEyeTracked), + OCULUS_BIND_ENTRY_POINT(GetFoveationEyeTrackedCenter), + OCULUS_BIND_ENTRY_POINT(GetGPUUtilSupported), + OCULUS_BIND_ENTRY_POINT(GetGPUUtilLevel), + OCULUS_BIND_ENTRY_POINT(SetThreadPerformance), + OCULUS_BIND_ENTRY_POINT(AutoThreadScheduling), + OCULUS_BIND_ENTRY_POINT(GetGPUFrameTime), + OCULUS_BIND_ENTRY_POINT(GetViewportStencil), + OCULUS_BIND_ENTRY_POINT(SendEvent), + OCULUS_BIND_ENTRY_POINT(SendEvent2), + OCULUS_BIND_ENTRY_POINT(AddCustomMetadata), + OCULUS_BIND_ENTRY_POINT(SetDeveloperMode), + OCULUS_BIND_ENTRY_POINT(GetCurrentTrackingTransformPose), + OCULUS_BIND_ENTRY_POINT(GetTrackingTransformRawPose), + OCULUS_BIND_ENTRY_POINT(GetTrackingTransformRelativePose), + OCULUS_BIND_ENTRY_POINT(GetTimeInSeconds), + //OCULUS_BIND_ENTRY_POINT(GetPTWNear), + OCULUS_BIND_ENTRY_POINT(GetASWVelocityScale), + OCULUS_BIND_ENTRY_POINT(GetASWDepthScale), + OCULUS_BIND_ENTRY_POINT(GetASWAdaptiveMode), + OCULUS_BIND_ENTRY_POINT(SetASWAdaptiveMode), + OCULUS_BIND_ENTRY_POINT(IsRequestingASWData), + OCULUS_BIND_ENTRY_POINT(GetPredictedDisplayTime), + OCULUS_BIND_ENTRY_POINT(GetHandTrackingEnabled), + OCULUS_BIND_ENTRY_POINT(GetHandState), + OCULUS_BIND_ENTRY_POINT(GetHandState2), + OCULUS_BIND_ENTRY_POINT(GetSkeleton2), + OCULUS_BIND_ENTRY_POINT(GetMesh), + OCULUS_BIND_ENTRY_POINT(GetLocalTrackingSpaceRecenterCount), + OCULUS_BIND_ENTRY_POINT(GetSystemHmd3DofModeEnabled), + OCULUS_BIND_ENTRY_POINT(SetClientColorDesc), + OCULUS_BIND_ENTRY_POINT(GetHmdColorDesc), + OCULUS_BIND_ENTRY_POINT(PollEvent), + OCULUS_BIND_ENTRY_POINT(GetNativeXrApiType), + OCULUS_BIND_ENTRY_POINT(GetLocalDimmingSupported), + OCULUS_BIND_ENTRY_POINT(SetLocalDimming), + OCULUS_BIND_ENTRY_POINT(GetCurrentInteractionProfile), +#ifndef OVRPLUGIN_JNI_LIB_EXCLUDED + OCULUS_BIND_ENTRY_POINT(GetSystemVolume2), + OCULUS_BIND_ENTRY_POINT(GetSystemHeadphonesPresent2), +#endif + + // Anchors + OCULUS_BIND_ENTRY_POINT(LocateSpace), + OCULUS_BIND_ENTRY_POINT(CreateSpatialAnchor), + OCULUS_BIND_ENTRY_POINT(DestroySpace), + OCULUS_BIND_ENTRY_POINT(SetSpaceComponentStatus), + OCULUS_BIND_ENTRY_POINT(GetSpaceComponentStatus), + OCULUS_BIND_ENTRY_POINT(EnumerateSpaceSupportedComponents), + OCULUS_BIND_ENTRY_POINT(QuerySpaces), + OCULUS_BIND_ENTRY_POINT(RetrieveSpaceQueryResults), + OCULUS_BIND_ENTRY_POINT(SaveSpace), + OCULUS_BIND_ENTRY_POINT(EraseSpace), + OCULUS_BIND_ENTRY_POINT(GetSpaceUuid), + + // Scene + OCULUS_BIND_ENTRY_POINT(GetSpaceContainer), + OCULUS_BIND_ENTRY_POINT(GetSpaceBoundingBox2D), + OCULUS_BIND_ENTRY_POINT(GetSpaceBoundingBox3D), + OCULUS_BIND_ENTRY_POINT(GetSpaceSemanticLabels), + OCULUS_BIND_ENTRY_POINT(GetSpaceRoomLayout), + OCULUS_BIND_ENTRY_POINT(GetSpaceBoundary2D), + OCULUS_BIND_ENTRY_POINT(RequestSceneCapture), + + // MovementSDK + OCULUS_BIND_ENTRY_POINT(GetBodyState), + OCULUS_BIND_ENTRY_POINT(GetBodyTrackingEnabled), + OCULUS_BIND_ENTRY_POINT(GetBodyTrackingSupported), + OCULUS_BIND_ENTRY_POINT(StartBodyTracking), + OCULUS_BIND_ENTRY_POINT(StopBodyTracking), + OCULUS_BIND_ENTRY_POINT(GetFaceTrackingEnabled), + OCULUS_BIND_ENTRY_POINT(GetFaceTrackingSupported), + OCULUS_BIND_ENTRY_POINT(GetFaceState), + OCULUS_BIND_ENTRY_POINT(StartFaceTracking), + OCULUS_BIND_ENTRY_POINT(StopFaceTracking), + OCULUS_BIND_ENTRY_POINT(GetEyeTrackingEnabled), + OCULUS_BIND_ENTRY_POINT(GetEyeTrackingSupported), + OCULUS_BIND_ENTRY_POINT(GetEyeGazesState), + OCULUS_BIND_ENTRY_POINT(StartEyeTracking), + OCULUS_BIND_ENTRY_POINT(StopEyeTracking), + + // OVR_Plugin_Insight.h + OCULUS_BIND_ENTRY_POINT(InitializeInsightPassthrough), + OCULUS_BIND_ENTRY_POINT(ShutdownInsightPassthrough), + OCULUS_BIND_ENTRY_POINT(GetInsightPassthroughInitialized), + OCULUS_BIND_ENTRY_POINT(GetInsightPassthroughInitializationState), + OCULUS_BIND_ENTRY_POINT(CreateInsightTriangleMesh), + OCULUS_BIND_ENTRY_POINT(DestroyInsightTriangleMesh), + OCULUS_BIND_ENTRY_POINT(AddInsightPassthroughSurfaceGeometry), + OCULUS_BIND_ENTRY_POINT(DestroyInsightPassthroughGeometryInstance), + OCULUS_BIND_ENTRY_POINT(UpdateInsightPassthroughGeometryTransform), + OCULUS_BIND_ENTRY_POINT(SetInsightPassthroughStyle), + OCULUS_BIND_ENTRY_POINT(GetPassthroughCapabilityFlags), + + // OVR_Plugin_MixedReality.h + + OCULUS_BIND_ENTRY_POINT(InitializeMixedReality), + OCULUS_BIND_ENTRY_POINT(ShutdownMixedReality), + OCULUS_BIND_ENTRY_POINT(GetMixedRealityInitialized), + OCULUS_BIND_ENTRY_POINT(UpdateExternalCamera), + OCULUS_BIND_ENTRY_POINT(GetExternalCameraCount), + OCULUS_BIND_ENTRY_POINT(GetExternalCameraName), + OCULUS_BIND_ENTRY_POINT(GetExternalCameraIntrinsics), + OCULUS_BIND_ENTRY_POINT(GetExternalCameraExtrinsics), + OCULUS_BIND_ENTRY_POINT(GetExternalCameraCalibrationRawPose), + OCULUS_BIND_ENTRY_POINT(OverrideExternalCameraFov), + OCULUS_BIND_ENTRY_POINT(GetUseOverriddenExternalCameraFov), + OCULUS_BIND_ENTRY_POINT(OverrideExternalCameraStaticPose), + OCULUS_BIND_ENTRY_POINT(GetUseOverriddenExternalCameraStaticPose), + OCULUS_BIND_ENTRY_POINT(GetExternalCameraPose), + OCULUS_BIND_ENTRY_POINT(ConvertPoseToCameraSpace), + OCULUS_BIND_ENTRY_POINT(ResetDefaultExternalCamera), + OCULUS_BIND_ENTRY_POINT(SetDefaultExternalCamera), + OCULUS_BIND_ENTRY_POINT(EnumerateAllCameraDevices), + OCULUS_BIND_ENTRY_POINT(EnumerateAvailableCameraDevices), + OCULUS_BIND_ENTRY_POINT(UpdateCameraDevices), + OCULUS_BIND_ENTRY_POINT(IsCameraDeviceAvailable2), + OCULUS_BIND_ENTRY_POINT(SetCameraDevicePreferredColorFrameSize), + OCULUS_BIND_ENTRY_POINT(OpenCameraDevice), + OCULUS_BIND_ENTRY_POINT(CloseCameraDevice), + OCULUS_BIND_ENTRY_POINT(HasCameraDeviceOpened2), + OCULUS_BIND_ENTRY_POINT(GetCameraDeviceIntrinsicsParameters), + OCULUS_BIND_ENTRY_POINT(IsCameraDeviceColorFrameAvailable2), + OCULUS_BIND_ENTRY_POINT(GetCameraDeviceColorFrameSize), + OCULUS_BIND_ENTRY_POINT(GetCameraDeviceColorFrameBgraPixels), + OCULUS_BIND_ENTRY_POINT(DoesCameraDeviceSupportDepth), + OCULUS_BIND_ENTRY_POINT(GetCameraDeviceDepthSensingMode), + OCULUS_BIND_ENTRY_POINT(SetCameraDeviceDepthSensingMode), + OCULUS_BIND_ENTRY_POINT(GetCameraDevicePreferredDepthQuality), + OCULUS_BIND_ENTRY_POINT(SetCameraDevicePreferredDepthQuality), + OCULUS_BIND_ENTRY_POINT(IsCameraDeviceDepthFrameAvailable), + OCULUS_BIND_ENTRY_POINT(GetCameraDeviceDepthFrameSize), + OCULUS_BIND_ENTRY_POINT(GetCameraDeviceDepthFramePixels), + OCULUS_BIND_ENTRY_POINT(GetCameraDeviceDepthConfidencePixels), + + // OVR_Plugin_Media.h + + OCULUS_BIND_ENTRY_POINT(Media_Initialize), + OCULUS_BIND_ENTRY_POINT(Media_Shutdown), + OCULUS_BIND_ENTRY_POINT(Media_GetInitialized), + OCULUS_BIND_ENTRY_POINT(Media_Update), + OCULUS_BIND_ENTRY_POINT(Media_GetMrcActivationMode), + OCULUS_BIND_ENTRY_POINT(Media_SetMrcActivationMode), + OCULUS_BIND_ENTRY_POINT(Media_IsMrcEnabled), + OCULUS_BIND_ENTRY_POINT(Media_IsMrcActivated), + OCULUS_BIND_ENTRY_POINT(Media_UseMrcDebugCamera), + OCULUS_BIND_ENTRY_POINT(Media_SetMrcInputVideoBufferType), + OCULUS_BIND_ENTRY_POINT(Media_GetMrcInputVideoBufferType), + OCULUS_BIND_ENTRY_POINT(Media_SetMrcFrameSize), + OCULUS_BIND_ENTRY_POINT(Media_GetMrcFrameSize), + OCULUS_BIND_ENTRY_POINT(Media_SetMrcAudioSampleRate), + OCULUS_BIND_ENTRY_POINT(Media_GetMrcAudioSampleRate), + OCULUS_BIND_ENTRY_POINT(Media_SetMrcFrameImageFlipped), + OCULUS_BIND_ENTRY_POINT(Media_GetMrcFrameImageFlipped), + OCULUS_BIND_ENTRY_POINT(Media_SetMrcFrameInverseAlpha), + OCULUS_BIND_ENTRY_POINT(Media_GetMrcFrameInverseAlpha), + OCULUS_BIND_ENTRY_POINT(Media_SetAvailableQueueIndexVulkan), + OCULUS_BIND_ENTRY_POINT(Media_EncodeMrcFrame), + OCULUS_BIND_ENTRY_POINT(Media_EncodeMrcFrameWithDualTextures), + OCULUS_BIND_ENTRY_POINT(Media_SyncMrcFrame), + OCULUS_BIND_ENTRY_POINT(Media_EncodeMrcFrameWithPoseTime), + OCULUS_BIND_ENTRY_POINT(Media_EncodeMrcFrameDualTexturesWithPoseTime), + OCULUS_BIND_ENTRY_POINT(Media_SetHeadsetControllerPose), + OCULUS_BIND_ENTRY_POINT(Media_EnumerateCameraAnchorHandles), + OCULUS_BIND_ENTRY_POINT(Media_GetCurrentCameraAnchorHandle), + OCULUS_BIND_ENTRY_POINT(Media_GetCameraAnchorName), + OCULUS_BIND_ENTRY_POINT(Media_GetCameraAnchorHandle), + OCULUS_BIND_ENTRY_POINT(Media_GetCameraAnchorType), + OCULUS_BIND_ENTRY_POINT(Media_CreateCustomCameraAnchor), + OCULUS_BIND_ENTRY_POINT(Media_DestroyCustomCameraAnchor), + OCULUS_BIND_ENTRY_POINT(Media_GetCustomCameraAnchorPose), + OCULUS_BIND_ENTRY_POINT(Media_SetCustomCameraAnchorPose), + OCULUS_BIND_ENTRY_POINT(Media_GetCameraMinMaxDistance), + OCULUS_BIND_ENTRY_POINT(Media_SetCameraMinMaxDistance), + }; + +#undef OCULUS_BIND_ENTRY_POINT + + bool result = true; + for (int i = 0; i < UE_ARRAY_COUNT(entryPointArray); ++i) + { + *(entryPointArray[i].EntryPointPtr) = LoadEntryPoint(LibraryHandle, entryPointArray[i].EntryPointName); + + if (*entryPointArray[i].EntryPointPtr == NULL) + { + UE_LOG(LogOculusPluginWrapper, Error, TEXT("OculusPlugin EntryPoint could not be loaded: %s"), ANSI_TO_TCHAR(entryPointArray[i].EntryPointName)); + result = false; + } + } + + wrapper->Initialized = true; + + if (result) + { + UE_LOG(LogOculusPluginWrapper, Log, TEXT("OculusPlugin initialized successfully")); + } + else + { + DestroyOculusPluginWrapper(wrapper); + } + + return result; +} + +void OculusPluginWrapper::DestroyOculusPluginWrapper(OculusPluginWrapper* wrapper) +{ + if (!wrapper->Initialized) + return; + + wrapper->Reset(); + + UE_LOG(LogOculusPluginWrapper, Log, TEXT("OculusPlugin destroyed successfully")); +} + +static void* LoadEntryPoint(void* Handle, const char* EntryPointName) +{ + if (Handle == nullptr) + return nullptr; + +#if PLATFORM_WINDOWS + void* ptr = GetProcAddress((HMODULE)Handle, EntryPointName); + if (ptr == nullptr) + { + UE_LOG(LogOculusPluginWrapper, Error, TEXT("Unable to load entry point: %s"), ANSI_TO_TCHAR(EntryPointName)); + } + return ptr; +#elif PLATFORM_ANDROID + void* ptr = dlsym(Handle, EntryPointName); + if (ptr == nullptr) + { + UE_LOG(LogOculusPluginWrapper, Error, TEXT("Unable to load entry point: %s, error %s"), ANSI_TO_TCHAR(EntryPointName), ANSI_TO_TCHAR(dlerror())); + } + return ptr; +#else + UE_LOG(LogOculusPluginWrapper, Error, TEXT("LoadEntryPoint: Unsupported platform")); + return nullptr; +#endif +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPluginWrapper.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPluginWrapper.h new file mode 100644 index 0000000000000000000000000000000000000000..a4d02824ad17fbede1d6eb056765d91105277b6a --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRPluginWrapper.h @@ -0,0 +1,391 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include <memory.h> + +#if PLATFORM_SUPPORTS_PRAGMA_PACK +#pragma pack (push,8) +#endif + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +#endif + +#pragma warning(push) +#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union +//#pragma warning(disable:4668) // 'symbol' is not defined as a preprocessor macro, replacing with '0' for 'directives' +#define OVRP_EXPORT typedef +#include "OVR_Plugin.h" +#include "OVR_Plugin_Insight.h" +#include "OVR_Plugin_MixedReality.h" +#include "OVR_Plugin_Media.h" +#undef OVRP_EXPORT +#pragma warning(pop) + +#if PLATFORM_WINDOWS +#include "Windows/HideWindowsPlatformTypes.h" +#endif + +#if PLATFORM_SUPPORTS_PRAGMA_PACK +#pragma pack (pop) +#endif + +#if PLATFORM_WINDOWS +#include "Windows/WindowsHWrapper.h" +#endif + +DECLARE_LOG_CATEGORY_EXTERN(LogOculusPluginWrapper, Log, All); + +#define OCULUS_DECLARE_ENTRY_POINT(Func) ovrp_##Func* Func + +struct OculusPluginWrapper +{ + OculusPluginWrapper() + { + Reset(); + } + + void Reset() + { + memset(this, 0, sizeof(OculusPluginWrapper)); + ovrpHeaderVersion.MajorVersion = OVRP_MAJOR_VERSION; + ovrpHeaderVersion.MinorVersion = OVRP_MINOR_VERSION; + ovrpHeaderVersion.PatchVersion = OVRP_PATCH_VERSION; + } + + bool IsInitialized() const + { + return Initialized; + } + + // OVR_Plugin.h + + OCULUS_DECLARE_ENTRY_POINT(PreInitialize5); + OCULUS_DECLARE_ENTRY_POINT(GetInitialized); + OCULUS_DECLARE_ENTRY_POINT(Initialize7); + OCULUS_DECLARE_ENTRY_POINT(Shutdown2); + OCULUS_DECLARE_ENTRY_POINT(GetVersion2); + OCULUS_DECLARE_ENTRY_POINT(GetNativeSDKVersion2); + OCULUS_DECLARE_ENTRY_POINT(GetNativeSDKPointer2); + OCULUS_DECLARE_ENTRY_POINT(GetDisplayAdapterId2); + OCULUS_DECLARE_ENTRY_POINT(GetAudioOutId2); + OCULUS_DECLARE_ENTRY_POINT(GetAudioOutDeviceId2); + OCULUS_DECLARE_ENTRY_POINT(GetAudioInId2); + OCULUS_DECLARE_ENTRY_POINT(GetAudioInDeviceId2); + OCULUS_DECLARE_ENTRY_POINT(GetInstanceExtensionsVk); + OCULUS_DECLARE_ENTRY_POINT(GetDeviceExtensionsVk); + OCULUS_DECLARE_ENTRY_POINT(SetupDistortionWindow3); + OCULUS_DECLARE_ENTRY_POINT(DestroyDistortionWindow2); + OCULUS_DECLARE_ENTRY_POINT(GetDominantHand); + OCULUS_DECLARE_ENTRY_POINT(SetRemoteHandedness); + OCULUS_DECLARE_ENTRY_POINT(SetColorScaleAndOffset); + OCULUS_DECLARE_ENTRY_POINT(SetupLayer); + OCULUS_DECLARE_ENTRY_POINT(SetupLayerDepth); + OCULUS_DECLARE_ENTRY_POINT(SetEyeFovPremultipliedAlphaMode); + OCULUS_DECLARE_ENTRY_POINT(GetEyeFovLayerId); + OCULUS_DECLARE_ENTRY_POINT(GetLayerTextureStageCount); + OCULUS_DECLARE_ENTRY_POINT(GetLayerTexture2); + OCULUS_DECLARE_ENTRY_POINT(GetLayerTextureFoveation); + OCULUS_DECLARE_ENTRY_POINT(GetLayerTextureSpaceWarp); + OCULUS_DECLARE_ENTRY_POINT(CalculateEyeLayerDesc3); + OCULUS_DECLARE_ENTRY_POINT(GetLayerAndroidSurfaceObject); + OCULUS_DECLARE_ENTRY_POINT(GetLayerOcclusionMesh); + OCULUS_DECLARE_ENTRY_POINT(DestroyLayer); + OCULUS_DECLARE_ENTRY_POINT(CalculateLayerDesc); + OCULUS_DECLARE_ENTRY_POINT(CalculateEyeLayerDesc2); + OCULUS_DECLARE_ENTRY_POINT(CalculateEyeViewportRect); + OCULUS_DECLARE_ENTRY_POINT(CalculateEyePreviewRect); + OCULUS_DECLARE_ENTRY_POINT(SetupMirrorTexture2); + OCULUS_DECLARE_ENTRY_POINT(DestroyMirrorTexture2); + OCULUS_DECLARE_ENTRY_POINT(GetAdaptiveGpuPerformanceScale2); + OCULUS_DECLARE_ENTRY_POINT(GetAppCpuStartToGpuEndTime2); + OCULUS_DECLARE_ENTRY_POINT(GetEyePixelsPerTanAngleAtCenter2); + OCULUS_DECLARE_ENTRY_POINT(GetHmdToEyeOffset2); + OCULUS_DECLARE_ENTRY_POINT(Update3); + OCULUS_DECLARE_ENTRY_POINT(WaitToBeginFrame); + OCULUS_DECLARE_ENTRY_POINT(BeginFrame4); + OCULUS_DECLARE_ENTRY_POINT(UpdateFoveation); + OCULUS_DECLARE_ENTRY_POINT(EndFrame4); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingOrientationSupported2); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingOrientationEnabled2); + OCULUS_DECLARE_ENTRY_POINT(SetTrackingOrientationEnabled2); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingPositionSupported2); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingPositionEnabled2); + OCULUS_DECLARE_ENTRY_POINT(SetTrackingPositionEnabled2); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingIPDEnabled2); + OCULUS_DECLARE_ENTRY_POINT(SetTrackingIPDEnabled2); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingCalibratedOrigin2); + OCULUS_DECLARE_ENTRY_POINT(SetTrackingCalibratedOrigin2); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingOriginType2); + OCULUS_DECLARE_ENTRY_POINT(SetTrackingOriginType2); + OCULUS_DECLARE_ENTRY_POINT(RecenterTrackingOrigin2); + OCULUS_DECLARE_ENTRY_POINT(GetNodePresent2); + OCULUS_DECLARE_ENTRY_POINT(GetNodeOrientationTracked2); + OCULUS_DECLARE_ENTRY_POINT(GetNodeOrientationValid); + OCULUS_DECLARE_ENTRY_POINT(GetNodePositionTracked2); + OCULUS_DECLARE_ENTRY_POINT(GetNodePositionValid); + OCULUS_DECLARE_ENTRY_POINT(SetNodePositionTracked2); + OCULUS_DECLARE_ENTRY_POINT(GetNodePoseState3); + OCULUS_DECLARE_ENTRY_POINT(GetNodePoseStateRaw); + OCULUS_DECLARE_ENTRY_POINT(GetNodeFrustum2); + OCULUS_DECLARE_ENTRY_POINT(SetHeadPoseModifier); + OCULUS_DECLARE_ENTRY_POINT(GetHeadPoseModifier); + OCULUS_DECLARE_ENTRY_POINT(GetControllerState4); + OCULUS_DECLARE_ENTRY_POINT(GetControllerState5); + OCULUS_DECLARE_ENTRY_POINT(GetActiveController2); + OCULUS_DECLARE_ENTRY_POINT(GetConnectedControllers2); + OCULUS_DECLARE_ENTRY_POINT(SetControllerVibration2); + OCULUS_DECLARE_ENTRY_POINT(SetControllerLocalizedVibration); + OCULUS_DECLARE_ENTRY_POINT(SetControllerHapticsAmplitudeEnvelope); + OCULUS_DECLARE_ENTRY_POINT(SetControllerHapticsPcm); + OCULUS_DECLARE_ENTRY_POINT(GetControllerHapticsDesc2); + OCULUS_DECLARE_ENTRY_POINT(GetControllerHapticsState2); + OCULUS_DECLARE_ENTRY_POINT(GetControllerSampleRateHz); + OCULUS_DECLARE_ENTRY_POINT(SetControllerHaptics2); + OCULUS_DECLARE_ENTRY_POINT(SetSuggestedCpuPerformanceLevel); + OCULUS_DECLARE_ENTRY_POINT(GetSuggestedCpuPerformanceLevel); + OCULUS_DECLARE_ENTRY_POINT(SetSuggestedGpuPerformanceLevel); + OCULUS_DECLARE_ENTRY_POINT(GetSuggestedGpuPerformanceLevel); + OCULUS_DECLARE_ENTRY_POINT(GetAppCPUPriority2); + OCULUS_DECLARE_ENTRY_POINT(SetAppCPUPriority2); + OCULUS_DECLARE_ENTRY_POINT(GetSystemPowerSavingMode2); + OCULUS_DECLARE_ENTRY_POINT(GetSystemDisplayFrequency2); + OCULUS_DECLARE_ENTRY_POINT(GetSystemDisplayAvailableFrequencies); + OCULUS_DECLARE_ENTRY_POINT(SetSystemDisplayFrequency); + OCULUS_DECLARE_ENTRY_POINT(GetSystemVSyncCount2); + OCULUS_DECLARE_ENTRY_POINT(SetSystemVSyncCount2); + OCULUS_DECLARE_ENTRY_POINT(GetSystemProductName2); + OCULUS_DECLARE_ENTRY_POINT(GetSystemRegion2); + OCULUS_DECLARE_ENTRY_POINT(ShowSystemUI2); + OCULUS_DECLARE_ENTRY_POINT(GetAppHasVrFocus2); + OCULUS_DECLARE_ENTRY_POINT(GetAppHasInputFocus); + OCULUS_DECLARE_ENTRY_POINT(GetAppHasSystemOverlayPresent); + OCULUS_DECLARE_ENTRY_POINT(GetAppShouldQuit2); + OCULUS_DECLARE_ENTRY_POINT(GetAppShouldRecenter2); + OCULUS_DECLARE_ENTRY_POINT(GetAppShouldRecreateDistortionWindow2); + OCULUS_DECLARE_ENTRY_POINT(GetAppLatencyTimings2); + OCULUS_DECLARE_ENTRY_POINT(SetAppEngineInfo2); + OCULUS_DECLARE_ENTRY_POINT(GetUserPresent2); + OCULUS_DECLARE_ENTRY_POINT(GetUserIPD2); + OCULUS_DECLARE_ENTRY_POINT(SetUserIPD2); + OCULUS_DECLARE_ENTRY_POINT(GetUserEyeHeight2); + OCULUS_DECLARE_ENTRY_POINT(SetUserEyeHeight2); + OCULUS_DECLARE_ENTRY_POINT(GetUserNeckEyeDistance2); + OCULUS_DECLARE_ENTRY_POINT(SetUserNeckEyeDistance2); + OCULUS_DECLARE_ENTRY_POINT(SetupDisplayObjects2); + OCULUS_DECLARE_ENTRY_POINT(GetSystemMultiViewSupported2); + OCULUS_DECLARE_ENTRY_POINT(GetEyeTextureArraySupported2); + OCULUS_DECLARE_ENTRY_POINT(GetBoundaryConfigured2); + OCULUS_DECLARE_ENTRY_POINT(GetDepthCompositingSupported); + OCULUS_DECLARE_ENTRY_POINT(TestBoundaryNode2); + OCULUS_DECLARE_ENTRY_POINT(TestBoundaryPoint2); + OCULUS_DECLARE_ENTRY_POINT(GetBoundaryGeometry3); + OCULUS_DECLARE_ENTRY_POINT(GetBoundaryDimensions2); + OCULUS_DECLARE_ENTRY_POINT(GetBoundaryVisible2); + OCULUS_DECLARE_ENTRY_POINT(SetBoundaryVisible2); + OCULUS_DECLARE_ENTRY_POINT(GetSystemHeadsetType2); + OCULUS_DECLARE_ENTRY_POINT(GetAppPerfStats2); + OCULUS_DECLARE_ENTRY_POINT(ResetAppPerfStats2); + OCULUS_DECLARE_ENTRY_POINT(GetAppFramerate2); + OCULUS_DECLARE_ENTRY_POINT(IsPerfMetricsSupported); + OCULUS_DECLARE_ENTRY_POINT(GetPerfMetricsFloat); + OCULUS_DECLARE_ENTRY_POINT(GetPerfMetricsInt); + OCULUS_DECLARE_ENTRY_POINT(SetHandNodePoseStateLatency); + OCULUS_DECLARE_ENTRY_POINT(GetHandNodePoseStateLatency); + OCULUS_DECLARE_ENTRY_POINT(GetSystemRecommendedMSAALevel2); + OCULUS_DECLARE_ENTRY_POINT(SetInhibitSystemUX2); + OCULUS_DECLARE_ENTRY_POINT(GetTiledMultiResSupported); + OCULUS_DECLARE_ENTRY_POINT(GetTiledMultiResLevel); + OCULUS_DECLARE_ENTRY_POINT(SetTiledMultiResLevel); + OCULUS_DECLARE_ENTRY_POINT(GetTiledMultiResDynamic); + OCULUS_DECLARE_ENTRY_POINT(SetTiledMultiResDynamic); + OCULUS_DECLARE_ENTRY_POINT(GetFoveationEyeTrackedSupported); + OCULUS_DECLARE_ENTRY_POINT(GetFoveationEyeTracked); + OCULUS_DECLARE_ENTRY_POINT(SetFoveationEyeTracked); + OCULUS_DECLARE_ENTRY_POINT(GetFoveationEyeTrackedCenter); + OCULUS_DECLARE_ENTRY_POINT(GetGPUUtilSupported); + OCULUS_DECLARE_ENTRY_POINT(GetGPUUtilLevel); + OCULUS_DECLARE_ENTRY_POINT(SetThreadPerformance); + OCULUS_DECLARE_ENTRY_POINT(AutoThreadScheduling); + OCULUS_DECLARE_ENTRY_POINT(GetGPUFrameTime); + OCULUS_DECLARE_ENTRY_POINT(GetViewportStencil); + OCULUS_DECLARE_ENTRY_POINT(SendEvent); + OCULUS_DECLARE_ENTRY_POINT(SendEvent2); + OCULUS_DECLARE_ENTRY_POINT(AddCustomMetadata); + OCULUS_DECLARE_ENTRY_POINT(SetDeveloperMode); + OCULUS_DECLARE_ENTRY_POINT(GetCurrentTrackingTransformPose); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingTransformRawPose); + OCULUS_DECLARE_ENTRY_POINT(GetTrackingTransformRelativePose); + OCULUS_DECLARE_ENTRY_POINT(GetTimeInSeconds); + //OCULUS_DECLARE_ENTRY_POINT(GetPTWNear); + OCULUS_DECLARE_ENTRY_POINT(GetASWVelocityScale); + OCULUS_DECLARE_ENTRY_POINT(GetASWDepthScale); + OCULUS_DECLARE_ENTRY_POINT(GetASWAdaptiveMode); + OCULUS_DECLARE_ENTRY_POINT(SetASWAdaptiveMode); + OCULUS_DECLARE_ENTRY_POINT(IsRequestingASWData); + OCULUS_DECLARE_ENTRY_POINT(GetPredictedDisplayTime); + OCULUS_DECLARE_ENTRY_POINT(GetHandTrackingEnabled); + OCULUS_DECLARE_ENTRY_POINT(GetHandState); + OCULUS_DECLARE_ENTRY_POINT(GetHandState2); + OCULUS_DECLARE_ENTRY_POINT(GetSkeleton2); + OCULUS_DECLARE_ENTRY_POINT(GetMesh); + OCULUS_DECLARE_ENTRY_POINT(GetLocalTrackingSpaceRecenterCount); + OCULUS_DECLARE_ENTRY_POINT(GetSystemHmd3DofModeEnabled); + OCULUS_DECLARE_ENTRY_POINT(SetClientColorDesc); + OCULUS_DECLARE_ENTRY_POINT(GetHmdColorDesc); + OCULUS_DECLARE_ENTRY_POINT(PollEvent); + + OCULUS_DECLARE_ENTRY_POINT(GetNativeXrApiType); + OCULUS_DECLARE_ENTRY_POINT(GetLocalDimmingSupported); + OCULUS_DECLARE_ENTRY_POINT(SetLocalDimming); + OCULUS_DECLARE_ENTRY_POINT(GetCurrentInteractionProfile); +#ifndef OVRPLUGIN_JNI_LIB_EXCLUDED + OCULUS_DECLARE_ENTRY_POINT(GetSystemVolume2); + OCULUS_DECLARE_ENTRY_POINT(GetSystemHeadphonesPresent2); +#endif + + // Anchors + OCULUS_DECLARE_ENTRY_POINT(LocateSpace); + OCULUS_DECLARE_ENTRY_POINT(CreateSpatialAnchor); + OCULUS_DECLARE_ENTRY_POINT(DestroySpace); + OCULUS_DECLARE_ENTRY_POINT(SetSpaceComponentStatus); + OCULUS_DECLARE_ENTRY_POINT(GetSpaceComponentStatus); + OCULUS_DECLARE_ENTRY_POINT(EnumerateSpaceSupportedComponents); + OCULUS_DECLARE_ENTRY_POINT(QuerySpaces); + OCULUS_DECLARE_ENTRY_POINT(RetrieveSpaceQueryResults); + OCULUS_DECLARE_ENTRY_POINT(SaveSpace); + OCULUS_DECLARE_ENTRY_POINT(EraseSpace); + OCULUS_DECLARE_ENTRY_POINT(GetSpaceUuid); + + // Scene + OCULUS_DECLARE_ENTRY_POINT(GetSpaceContainer); + OCULUS_DECLARE_ENTRY_POINT(GetSpaceBoundingBox2D); + OCULUS_DECLARE_ENTRY_POINT(GetSpaceBoundingBox3D); + OCULUS_DECLARE_ENTRY_POINT(GetSpaceSemanticLabels); + OCULUS_DECLARE_ENTRY_POINT(GetSpaceRoomLayout); + OCULUS_DECLARE_ENTRY_POINT(GetSpaceBoundary2D); + OCULUS_DECLARE_ENTRY_POINT(RequestSceneCapture); + + // MovementSDK + OCULUS_DECLARE_ENTRY_POINT(GetBodyState); + OCULUS_DECLARE_ENTRY_POINT(GetBodyTrackingEnabled); + OCULUS_DECLARE_ENTRY_POINT(GetBodyTrackingSupported); + OCULUS_DECLARE_ENTRY_POINT(StartBodyTracking); + OCULUS_DECLARE_ENTRY_POINT(StopBodyTracking); + OCULUS_DECLARE_ENTRY_POINT(GetFaceTrackingEnabled); + OCULUS_DECLARE_ENTRY_POINT(GetFaceTrackingSupported); + OCULUS_DECLARE_ENTRY_POINT(GetFaceState); + OCULUS_DECLARE_ENTRY_POINT(StartFaceTracking); + OCULUS_DECLARE_ENTRY_POINT(StopFaceTracking); + OCULUS_DECLARE_ENTRY_POINT(GetEyeTrackingEnabled); + OCULUS_DECLARE_ENTRY_POINT(GetEyeTrackingSupported); + OCULUS_DECLARE_ENTRY_POINT(GetEyeGazesState); + OCULUS_DECLARE_ENTRY_POINT(StartEyeTracking); + OCULUS_DECLARE_ENTRY_POINT(StopEyeTracking); + + //OVR_Plugin_Insight.h + OCULUS_DECLARE_ENTRY_POINT(InitializeInsightPassthrough); + OCULUS_DECLARE_ENTRY_POINT(ShutdownInsightPassthrough); + OCULUS_DECLARE_ENTRY_POINT(GetInsightPassthroughInitialized); + OCULUS_DECLARE_ENTRY_POINT(GetInsightPassthroughInitializationState); + OCULUS_DECLARE_ENTRY_POINT(CreateInsightTriangleMesh); + OCULUS_DECLARE_ENTRY_POINT(DestroyInsightTriangleMesh); + OCULUS_DECLARE_ENTRY_POINT(AddInsightPassthroughSurfaceGeometry); + OCULUS_DECLARE_ENTRY_POINT(DestroyInsightPassthroughGeometryInstance); + OCULUS_DECLARE_ENTRY_POINT(UpdateInsightPassthroughGeometryTransform); + OCULUS_DECLARE_ENTRY_POINT(SetInsightPassthroughStyle); + OCULUS_DECLARE_ENTRY_POINT(GetPassthroughCapabilityFlags); + + //OVR_Plugin_MixedReality.h + + OCULUS_DECLARE_ENTRY_POINT(InitializeMixedReality); + OCULUS_DECLARE_ENTRY_POINT(ShutdownMixedReality); + OCULUS_DECLARE_ENTRY_POINT(GetMixedRealityInitialized); + OCULUS_DECLARE_ENTRY_POINT(UpdateExternalCamera); + OCULUS_DECLARE_ENTRY_POINT(GetExternalCameraCount); + OCULUS_DECLARE_ENTRY_POINT(GetExternalCameraName); + OCULUS_DECLARE_ENTRY_POINT(GetExternalCameraIntrinsics); + OCULUS_DECLARE_ENTRY_POINT(GetExternalCameraExtrinsics); + OCULUS_DECLARE_ENTRY_POINT(GetExternalCameraCalibrationRawPose); + OCULUS_DECLARE_ENTRY_POINT(OverrideExternalCameraFov); + OCULUS_DECLARE_ENTRY_POINT(GetUseOverriddenExternalCameraFov); + OCULUS_DECLARE_ENTRY_POINT(OverrideExternalCameraStaticPose); + OCULUS_DECLARE_ENTRY_POINT(GetUseOverriddenExternalCameraStaticPose); + OCULUS_DECLARE_ENTRY_POINT(GetExternalCameraPose); + OCULUS_DECLARE_ENTRY_POINT(ConvertPoseToCameraSpace); + OCULUS_DECLARE_ENTRY_POINT(ResetDefaultExternalCamera); + OCULUS_DECLARE_ENTRY_POINT(SetDefaultExternalCamera); + OCULUS_DECLARE_ENTRY_POINT(EnumerateAllCameraDevices); + OCULUS_DECLARE_ENTRY_POINT(EnumerateAvailableCameraDevices); + OCULUS_DECLARE_ENTRY_POINT(UpdateCameraDevices); + OCULUS_DECLARE_ENTRY_POINT(IsCameraDeviceAvailable2); + OCULUS_DECLARE_ENTRY_POINT(SetCameraDevicePreferredColorFrameSize); + OCULUS_DECLARE_ENTRY_POINT(OpenCameraDevice); + OCULUS_DECLARE_ENTRY_POINT(CloseCameraDevice); + OCULUS_DECLARE_ENTRY_POINT(HasCameraDeviceOpened2); + OCULUS_DECLARE_ENTRY_POINT(GetCameraDeviceIntrinsicsParameters); + OCULUS_DECLARE_ENTRY_POINT(IsCameraDeviceColorFrameAvailable2); + OCULUS_DECLARE_ENTRY_POINT(GetCameraDeviceColorFrameSize); + OCULUS_DECLARE_ENTRY_POINT(GetCameraDeviceColorFrameBgraPixels); + OCULUS_DECLARE_ENTRY_POINT(DoesCameraDeviceSupportDepth); + OCULUS_DECLARE_ENTRY_POINT(GetCameraDeviceDepthSensingMode); + OCULUS_DECLARE_ENTRY_POINT(SetCameraDeviceDepthSensingMode); + OCULUS_DECLARE_ENTRY_POINT(GetCameraDevicePreferredDepthQuality); + OCULUS_DECLARE_ENTRY_POINT(SetCameraDevicePreferredDepthQuality); + OCULUS_DECLARE_ENTRY_POINT(IsCameraDeviceDepthFrameAvailable); + OCULUS_DECLARE_ENTRY_POINT(GetCameraDeviceDepthFrameSize); + OCULUS_DECLARE_ENTRY_POINT(GetCameraDeviceDepthFramePixels); + OCULUS_DECLARE_ENTRY_POINT(GetCameraDeviceDepthConfidencePixels); + + // OVR_Plugin_Media.h + + OCULUS_DECLARE_ENTRY_POINT(Media_Initialize); + OCULUS_DECLARE_ENTRY_POINT(Media_Shutdown); + OCULUS_DECLARE_ENTRY_POINT(Media_GetInitialized); + OCULUS_DECLARE_ENTRY_POINT(Media_Update); + OCULUS_DECLARE_ENTRY_POINT(Media_GetMrcActivationMode); + OCULUS_DECLARE_ENTRY_POINT(Media_SetMrcActivationMode); + OCULUS_DECLARE_ENTRY_POINT(Media_IsMrcEnabled); + OCULUS_DECLARE_ENTRY_POINT(Media_IsMrcActivated); + OCULUS_DECLARE_ENTRY_POINT(Media_UseMrcDebugCamera); + OCULUS_DECLARE_ENTRY_POINT(Media_SetMrcInputVideoBufferType); + OCULUS_DECLARE_ENTRY_POINT(Media_GetMrcInputVideoBufferType); + OCULUS_DECLARE_ENTRY_POINT(Media_SetMrcFrameSize); + OCULUS_DECLARE_ENTRY_POINT(Media_GetMrcFrameSize); + OCULUS_DECLARE_ENTRY_POINT(Media_SetMrcAudioSampleRate); + OCULUS_DECLARE_ENTRY_POINT(Media_GetMrcAudioSampleRate); + OCULUS_DECLARE_ENTRY_POINT(Media_SetMrcFrameImageFlipped); + OCULUS_DECLARE_ENTRY_POINT(Media_GetMrcFrameImageFlipped); + OCULUS_DECLARE_ENTRY_POINT(Media_SetMrcFrameInverseAlpha); + OCULUS_DECLARE_ENTRY_POINT(Media_GetMrcFrameInverseAlpha); + OCULUS_DECLARE_ENTRY_POINT(Media_SetAvailableQueueIndexVulkan); + OCULUS_DECLARE_ENTRY_POINT(Media_EncodeMrcFrame); + OCULUS_DECLARE_ENTRY_POINT(Media_EncodeMrcFrameWithDualTextures); + OCULUS_DECLARE_ENTRY_POINT(Media_SyncMrcFrame); + OCULUS_DECLARE_ENTRY_POINT(Media_EncodeMrcFrameWithPoseTime); + OCULUS_DECLARE_ENTRY_POINT(Media_EncodeMrcFrameDualTexturesWithPoseTime); + OCULUS_DECLARE_ENTRY_POINT(Media_SetHeadsetControllerPose); + OCULUS_DECLARE_ENTRY_POINT(Media_EnumerateCameraAnchorHandles); + OCULUS_DECLARE_ENTRY_POINT(Media_GetCurrentCameraAnchorHandle); + OCULUS_DECLARE_ENTRY_POINT(Media_GetCameraAnchorName); + OCULUS_DECLARE_ENTRY_POINT(Media_GetCameraAnchorHandle); + OCULUS_DECLARE_ENTRY_POINT(Media_GetCameraAnchorType); + OCULUS_DECLARE_ENTRY_POINT(Media_CreateCustomCameraAnchor); + OCULUS_DECLARE_ENTRY_POINT(Media_DestroyCustomCameraAnchor); + OCULUS_DECLARE_ENTRY_POINT(Media_GetCustomCameraAnchorPose); + OCULUS_DECLARE_ENTRY_POINT(Media_SetCustomCameraAnchorPose); + OCULUS_DECLARE_ENTRY_POINT(Media_GetCameraMinMaxDistance); + OCULUS_DECLARE_ENTRY_POINT(Media_SetCameraMinMaxDistance); + + static bool InitializeOculusPluginWrapper(OculusPluginWrapper* wrapper); + static void DestroyOculusPluginWrapper(OculusPluginWrapper* wrapper); + +private: + ovrpVersion ovrpHeaderVersion; + bool Initialized; +}; + +#undef OCULUS_DECLARE_ENTRY_POINT + diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRResourceHolder.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRResourceHolder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e17b297c305bc9fb7c51b35c355bf5097eaed6fd --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRResourceHolder.cpp @@ -0,0 +1,42 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRResourceHolder.h" +#include "HeadMountedDisplayTypes.h" // for LogHMD +#include "UObject/ConstructorHelpers.h" +#include "Materials/Material.h" + +////////////////////////////////////////////////////////////////////////// +// UOculusResourceManager + +UOculusXRResourceHolder::UOculusXRResourceHolder(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + static ConstructorHelpers::FObjectFinder<UMaterial> StaticPokeAHoleMaterial(TEXT("/OculusXR/Materials/PokeAHoleMaterial")); + + PokeAHoleMaterial = StaticPokeAHoleMaterial.Object; + + if (!PokeAHoleMaterial) + { + UE_LOG(LogHMD, Error, TEXT("Unable to load PokeAHoleMaterial")); + } + else + { + UE_LOG(LogHMD, Log, TEXT("PokeAHoleMaterial loaded successfully")); + } + + static ConstructorHelpers::FObjectFinder<UMaterial> StaticPokeAHoleInverseMaterial(TEXT("/OculusXR/Materials/PokeAHoleInverseMaterial")); + + PokeAHoleInverseMaterial = StaticPokeAHoleInverseMaterial.Object; + + if (!PokeAHoleInverseMaterial) + { + UE_LOG(LogHMD, Error, TEXT("Unable to load PokeAHoleInverseMaterial")); + } + else + { + UE_LOG(LogHMD, Log, TEXT("PokeAHoleInverseMaterial loaded successfully")); + } + + + +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRResourceHolder.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRResourceHolder.h new file mode 100644 index 0000000000000000000000000000000000000000..eaa7ee58bdb5e9d64ecdaae79e20b94edd37f81f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRResourceHolder.h @@ -0,0 +1,22 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "Materials/MaterialInterface.h" +#include "OculusXRResourceHolder.generated.h" + +/** + * + */ +UCLASS() +class UOculusXRResourceHolder : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + UPROPERTY() + UMaterial* PokeAHoleMaterial; + UMaterial* PokeAHoleInverseMaterial; +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRSceneCaptureCubemap.cpp b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRSceneCaptureCubemap.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e497585acd109e99f72684bdcec66e24e374af42 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRSceneCaptureCubemap.cpp @@ -0,0 +1,193 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRSceneCaptureCubemap.h" +#include "OculusXRHMDPrivate.h" +#include "IImageWrapper.h" +#include "IImageWrapperModule.h" +#include "Kismet/GameplayStatics.h" +#include "GameFramework/PlayerController.h" +#include "Components/SceneCaptureComponent2D.h" +#include "Engine/World.h" +#include "Engine/StaticMeshActor.h" +#include "Engine/TextureRenderTarget2D.h" +#include "TextureResource.h" +#include "HAL/FileManager.h" +#include "Misc/FileHelper.h" +#include "XRThreadUtils.h" + +//------------------------------------------------------------------------------------------------- +// UOculusXRSceneCaptureCubemap +//------------------------------------------------------------------------------------------------- + +UOculusXRSceneCaptureCubemap::UOculusXRSceneCaptureCubemap() + : Stage(None) + , CaptureBoxSideRes(2048) + , CaptureFormat(EPixelFormat::PF_A16B16G16R16) + , OverriddenLocation(FVector::ZeroVector) + , OverriddenOrientation(FQuat::Identity) + , CaptureOffset(FVector::ZeroVector) +{ +} + +void UOculusXRSceneCaptureCubemap::StartCapture(UWorld* World, uint32 InCaptureBoxSideRes, EPixelFormat InFormat) +{ + CaptureBoxSideRes = InCaptureBoxSideRes; + CaptureFormat = InFormat; + + FVector Location = OverriddenLocation; + FQuat Orientation= OverriddenOrientation; + + APlayerController* CapturePlayerController = UGameplayStatics::GetPlayerController(GWorld, 0); + if (CapturePlayerController) + { + FRotator Rotation; + CapturePlayerController->GetPlayerViewPoint(Location, Rotation); + Rotation.Pitch = Rotation.Roll = 0; + Orientation = FQuat(Rotation); + + Location += CaptureOffset; + } + + if (!OverriddenOrientation.IsIdentity()) + { + Orientation = OverriddenOrientation; + } + if (!OverriddenLocation.IsZero()) + { + Location = OverriddenLocation; + } + + const FVector ZAxis(0, 0, 1); + const FVector YAxis(0, 1, 0); + const FQuat FaceOrientations[]= { {ZAxis, PI/2}, { ZAxis, -PI/2}, // right, left + {YAxis, -PI/2}, { YAxis, PI/2}, // top, bottom + {ZAxis, 0}, { ZAxis, -PI} }; // front, back + + for (int i = 0; i < 6; ++i) + { + USceneCaptureComponent2D* CaptureComponent = NewObject<USceneCaptureComponent2D>(); + CaptureComponent->SetVisibility(true); + CaptureComponent->SetHiddenInGame(false); + + CaptureComponent->FOVAngle = 90.f; + CaptureComponent->bCaptureEveryFrame = true; + CaptureComponent->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR; + + const FName TargetName = MakeUniqueObjectName(this, UTextureRenderTarget2D::StaticClass(), TEXT("SceneCaptureTextureTarget")); + CaptureComponent->TextureTarget = NewObject<UTextureRenderTarget2D>(this, TargetName); + CaptureComponent->TextureTarget->InitCustomFormat(CaptureBoxSideRes, CaptureBoxSideRes, CaptureFormat, false); + + CaptureComponents.Add(CaptureComponent); + + CaptureComponent->RegisterComponentWithWorld(GWorld); + + CaptureComponent->SetWorldLocationAndRotation(Location, Orientation * FaceOrientations[i]); + CaptureComponent->UpdateContent(); + } + Stage = SettingPos; + + FActorSpawnParameters SpawnInfo; + SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + SpawnInfo.bNoFail = true; + SpawnInfo.ObjectFlags = RF_Transient; + + AStaticMeshActor* InGameActor; + InGameActor = World->SpawnActor<AStaticMeshActor>(SpawnInfo); + + OutputDir = FPaths::ProjectSavedDir() + TEXT("/Cubemaps"); + IFileManager::Get().MakeDirectory(*OutputDir); +} + +void UOculusXRSceneCaptureCubemap::Tick(float DeltaTime) +{ + ExecuteOnRenderThread([]() + { + TickRenderingTickables(); + }); + + if (Stage == SettingPos) + { + Stage = Capturing; + return; + } + + //Read Whole Capture Buffer + IImageWrapperModule& ImageWrapperModule = FModuleManager::LoadModuleChecked<IImageWrapperModule>(FName("ImageWrapper")); + TSharedPtr<IImageWrapper> ImageWrapper = ImageWrapperModule.CreateImageWrapper(EImageFormat::PNG); + + TArray<FColor> OneFaceSurface, WholeCubemapData; + OneFaceSurface.AddUninitialized(CaptureBoxSideRes * CaptureBoxSideRes); + WholeCubemapData.AddUninitialized(CaptureBoxSideRes * 6 * CaptureBoxSideRes); + // Read pixels + for (int cubeFaceIdx = 0; cubeFaceIdx < 6; ++cubeFaceIdx) + { + auto RenderTarget = CaptureComponents[cubeFaceIdx]->TextureTarget->GameThread_GetRenderTargetResource(); + RenderTarget->ReadPixelsPtr(OneFaceSurface.GetData(), FReadSurfaceDataFlags()); + + // enforce alpha to be 1 + for (FColor& Color : OneFaceSurface) + { + Color.A = 255; + } + + // copy subimage into whole cubemap array + const uint32 Stride = CaptureBoxSideRes * 6; + const uint32 XOff = cubeFaceIdx*CaptureBoxSideRes; + const uint32 StripSizeInBytes = CaptureBoxSideRes * sizeof(FColor); + for (uint32 y = 0; y < CaptureBoxSideRes; ++y) + { + FMemory::Memcpy(WholeCubemapData.GetData() + XOff + y * Stride, OneFaceSurface.GetData() + y * CaptureBoxSideRes, StripSizeInBytes); + } + } + + ImageWrapper->SetRaw(WholeCubemapData.GetData(), WholeCubemapData.GetAllocatedSize(), CaptureBoxSideRes * 6, CaptureBoxSideRes, ERGBFormat::BGRA, 8); + const TArray64<uint8>& PNGData = ImageWrapper->GetCompressed(100); + + const FString Filename = OutputDir + FString::Printf(TEXT("/Cubemap-%d-%s.png"), CaptureBoxSideRes, *FDateTime::Now().ToString(TEXT("%m.%d-%H.%M.%S"))); + + FFileHelper::SaveArrayToFile(PNGData, *Filename); + + check (Stage == Capturing); + Stage = Finished; + for (int i = 0; i < CaptureComponents.Num(); ++i) + { + CaptureComponents[i]->UnregisterComponent(); + } + CaptureComponents.SetNum(0); + RemoveFromRoot(); // We're done here, so remove ourselves from the root set. @TODO: Fix this later +} + + +#if !UE_BUILD_SHIPPING +void UOculusXRSceneCaptureCubemap::CaptureCubemapCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar) +{ + bool bCreateOculusMobileCubemap = false; + FVector CaptureOffset(FVector::ZeroVector); + float Yaw = 0.f; + for (const FString& Arg : Args) + { + FParse::Value(*Arg, TEXT("XOFF="), CaptureOffset.X); + FParse::Value(*Arg, TEXT("YOFF="), CaptureOffset.Y); + FParse::Value(*Arg, TEXT("ZOFF="), CaptureOffset.Z); + FParse::Value(*Arg, TEXT("YAW="), Yaw); + + if (Arg.Equals(TEXT("MOBILE"), ESearchCase::IgnoreCase)) + { + bCreateOculusMobileCubemap = true; + } + } + + UOculusXRSceneCaptureCubemap* CubemapCapturer = NewObject<UOculusXRSceneCaptureCubemap>(); + CubemapCapturer->AddToRoot(); // TODO: Don't add the object to the GC root + CubemapCapturer->SetOffset((FVector)CaptureOffset); + if (Yaw != 0.f) + { + FRotator Rotation(FRotator::ZeroRotator); + Rotation.Yaw = Yaw; + const FQuat Orient(Rotation); + CubemapCapturer->SetInitialOrientation(Orient); + } + const uint32 CaptureHeight = 2048; + CubemapCapturer->StartCapture(World, bCreateOculusMobileCubemap ? CaptureHeight / 2 : CaptureHeight); +} +#endif diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRSceneCaptureCubemap.h b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRSceneCaptureCubemap.h new file mode 100644 index 0000000000000000000000000000000000000000..d3501cc6b171f97eaba1f0fcda5128489d6d6a57 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Private/OculusXRSceneCaptureCubemap.h @@ -0,0 +1,80 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDPrivate.h" +#include "UObject/ObjectMacros.h" +#include "Tickable.h" +#include "OculusXRSceneCaptureCubemap.generated.h" + + +//------------------------------------------------------------------------------------------------- +// UOculusXRSceneCaptureCubemap +//------------------------------------------------------------------------------------------------- + +class USceneCaptureComponent2D; + +UCLASS() +class UOculusXRSceneCaptureCubemap : public UObject, public FTickableGameObject +{ + GENERATED_BODY() +public: + UOculusXRSceneCaptureCubemap(); + + virtual void Tick(float DeltaTime) override; + + virtual bool IsTickable() const override + { + return CaptureComponents.Num() != 0 && Stage != None; + } + + virtual bool IsTickableWhenPaused() const override + { + return IsTickable(); + } + + virtual TStatId GetStatId() const + { + RETURN_QUICK_DECLARE_CYCLE_STAT(USceneCapturer, STATGROUP_Tickables); + } + + // init capture params and start + void StartCapture(UWorld* World, uint32 InCaptureBoxSideRes, EPixelFormat InFormat = EPixelFormat::PF_A16B16G16R16); + + // sets offset for the capture, in UU, relatively to current player 0 location + void SetOffset(FVector InOffset) { CaptureOffset = InOffset; } + + // overrides player's 0 orientation for the capture. + void SetInitialOrientation(const FQuat& InOrientation) { OverriddenOrientation = InOrientation; } + + // overrides player's 0 location for the capture. + void SetInitialLocation(FVector InLocation) { OverriddenLocation = InLocation; } + + bool IsFinished() const { return Stage == Finished; } + bool IsCapturing() const { return Stage == Capturing || Stage == SettingPos; } + +#if !UE_BUILD_SHIPPING + static void CaptureCubemapCommandHandler(const TArray<FString>& Args, UWorld* World, FOutputDevice& Ar); +#endif // UE_BUILD_SHIPPING + +private: + + enum EStage + { + None, + SettingPos, + Capturing, + Finished + } Stage; + + UPROPERTY() + TArray<USceneCaptureComponent2D*> CaptureComponents; + + uint32 CaptureBoxSideRes; + EPixelFormat CaptureFormat; + + FString OutputDir; + + FVector OverriddenLocation; // overridden location of the capture, world coordinates, UU + FQuat OverriddenOrientation; // overridden orientation of the capture. Full orientation is used (not only yaw, like with player's rotation). + FVector CaptureOffset; // offset relative to current player's 0 location +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/IOculusXRHMDModule.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/IOculusXRHMDModule.h new file mode 100644 index 0000000000000000000000000000000000000000..2a1c24661ec65b9e5dcf723cb886c8d9e4bc839f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/IOculusXRHMDModule.h @@ -0,0 +1,132 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Modules/ModuleManager.h" +#include "IHeadMountedDisplayModule.h" +#include "HeadMountedDisplayTypes.h" + +// Oculus support is not available on Windows XP +#define OCULUS_HMD_SUPPORTED_PLATFORMS (PLATFORM_WINDOWS && WINVER > 0x0502) || (PLATFORM_ANDROID_ARM || PLATFORM_ANDROID_ARM64) + +//------------------------------------------------------------------------------------------------- +// IOculusXRHMDModule +//------------------------------------------------------------------------------------------------- + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IOculusXRHMDModule : public IHeadMountedDisplayModule +{ +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 IOculusXRHMDModule& Get() + { + return FModuleManager::LoadModuleChecked< IOculusXRHMDModule >("OculusXRHMD"); + } + + /** + * 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("OculusXRHMD"); + } + + /** + * Grabs the current orientation and position for the HMD. If positional tracking is not available, DevicePosition will be a zero vector + * + * @param DeviceRotation (out) The device's current rotation + * @param DevicePosition (out) The device's current position, in its own tracking space + * @param NeckPosition (out) The estimated neck position, calculated using NeckToEye vector from User Profile. Same coordinate space as DevicePosition. + * @param bUseOrienationForPlayerCamera (in) Should be set to 'true' if the orientation is going to be used to update orientation of the camera manually. + * @param bUsePositionForPlayerCamera (in) Should be set to 'true' if the position is going to be used to update position of the camera manually. + * @param PositionScale (in) The 3D scale that will be applied to position. + */ + virtual void GetPose(FRotator& DeviceRotation, FVector& DevicePosition, FVector& NeckPosition, bool bUseOrienationForPlayerCamera = false, bool bUsePositionForPlayerCamera = false, const FVector PositionScale = FVector::ZeroVector) = 0; + + /** + * Reports raw sensor data. If HMD doesn't support any of the parameters then it will be set to zero. + * + * @param AngularAcceleration (out) Angular acceleration in radians per second per second. + * @param LinearAcceleration (out) Acceleration in meters per second per second. + * @param AngularVelocity (out) Angular velocity in radians per second. + * @param LinearVelocity (out) Velocity in meters per second. + * @param TimeInSeconds (out) Time when the reported IMU reading took place, in seconds. + */ + virtual void GetRawSensorData(FVector& AngularAcceleration, FVector& LinearAcceleration, FVector& AngularVelocity, FVector& LinearVelocity, float& TimeInSeconds) = 0; + + /** + * Returns current user profile. + * + * @param Profile (out) Structure to hold current user profile. + * @return (boolean) True, if user profile was acquired. + */ + virtual bool GetUserProfile(struct FOculusXRHmdUserProfile& Profile)=0; + + /** + * Sets 'base rotation' - the rotation that will be subtracted from + * the actual HMD orientation. + * Sets base position offset (in meters). The base position offset is the distance from the physical (0, 0, 0) position + * to current HMD position (bringing the (0, 0, 0) point to the current HMD position) + * Note, this vector is set by ResetPosition call; use this method with care. + * The axis of the vector are the same as in Unreal: X - forward, Y - right, Z - up. + * + * @param Rotation (in) Rotator object with base rotation + * @param BaseOffsetInMeters (in) the vector to be set as base offset, in meters. + * @param Options (in) specifies either position, orientation or both should be set. + */ + virtual void SetBaseRotationAndBaseOffsetInMeters(FRotator Rotation, FVector BaseOffsetInMeters, EOrientPositionSelector::Type Options) = 0; + + /** + * Returns current base rotation and base offset. + * The base offset is currently used base position offset, previously set by the + * ResetPosition or SetBasePositionOffset calls. It represents a vector that translates the HMD's position + * into (0,0,0) point, in meters. + * The axis of the vector are the same as in Unreal: X - forward, Y - right, Z - up. + * + * @param OutRotation (out) Rotator object with base rotation + * @param OutBaseOffsetInMeters (out) base position offset, vector, in meters. + */ + virtual void GetBaseRotationAndBaseOffsetInMeters(FRotator& OutRotation, FVector& OutBaseOffsetInMeters) = 0; + + /** + * Sets 'base rotation' - the rotation that will be subtracted from + * the actual HMD orientation. + * The position offset might be added to current HMD position, + * effectively moving the virtual camera by the specified offset. The addition + * occurs after the HMD orientation and position are applied. + * + * @param BaseRot (in) Rotator object with base rotation + * @param PosOffset (in) the vector to be added to HMD position. + * @param Options (in) specifies either position, orientation or both should be set. + */ + virtual void SetBaseRotationAndPositionOffset(FRotator BaseRot, FVector PosOffset, EOrientPositionSelector::Type Options) = 0; + + /** + * Returns current base rotation and position offset. + * + * @param OutRot (out) Rotator object with base rotation + * @param OutPosOffset (out) the vector with previously set position offset. + */ + virtual void GetBaseRotationAndPositionOffset(FRotator& OutRot, FVector& OutPosOffset) = 0; + + /** + * Returns IStereoLayers interface to work with overlays. + */ + virtual class IStereoLayers* GetStereoLayers() = 0; + + virtual FString GetDeviceSystemName() = 0; + +#if OCULUS_HMD_SUPPORTED_PLATFORMS + virtual bool PoseToOrientationAndPosition(const FQuat& InOrientation, const FVector& InPosition, FQuat& OutOrientation, FVector& OutPosition) const = 0; +#endif //OCULUS_HMD_SUPPORTED_PLATFORMS +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRAssetDirectory.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRAssetDirectory.h new file mode 100644 index 0000000000000000000000000000000000000000..6c3356783881191c1a71023889eca433c73342d7 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRAssetDirectory.h @@ -0,0 +1,17 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/SoftObjectPath.h" + +class FOculusAssetDirectory +{ +public: +#if WITH_EDITORONLY_DATA + OCULUSXRHMD_API static void LoadForCook(); + OCULUSXRHMD_API static void ReleaseAll(); +#endif + + static FSoftObjectPath AssetListing[]; +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXREventComponent.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXREventComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..3b6165ebe17d44c087922644d40740204695daa0 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXREventComponent.h @@ -0,0 +1,34 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +// OculusEventComponent.h: Component to handle receiving events from Oculus HMDs + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Components/ActorComponent.h" +#include "OculusXREventComponent.generated.h" + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = OculusXRHMD) +class OCULUSXRHMD_API UOculusXREventComponent : public UActorComponent +{ + GENERATED_BODY() + +public: + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams(FOculusDisplayRefreshRateChangedEventDelegate, float, fromRefreshRate, float, toRefreshRate); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam(FOculusEyeTrackingStateChangedEventDelegate, bool, bEyeTrackingOn); + + UPROPERTY(BlueprintAssignable) + FOculusDisplayRefreshRateChangedEventDelegate OculusDisplayRefreshRateChanged; + + UPROPERTY(BlueprintAssignable) + FOculusEyeTrackingStateChangedEventDelegate OculusEyeTrackingStateChanged; + + void OnRegister() override; + void OnUnregister() override; + +private: + /** Native handlers that get registered with the actual FCoreDelegates, and then proceed to broadcast to the delegates above */ + void OculusDisplayRefreshRateChanged_Handler(float fromRefresh, float toRefresh) { OculusDisplayRefreshRateChanged.Broadcast(fromRefresh, toRefresh); } + void OculusEyeTrackingStateChanged_Handler(bool bEyeTrackingOn) { OculusEyeTrackingStateChanged.Broadcast(bEyeTrackingOn); } +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRFunctionLibrary.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRFunctionLibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..0234bf8c665289632bd5b01924d4fe509e34fbff --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRFunctionLibrary.h @@ -0,0 +1,601 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "IHeadMountedDisplay.h" +#include "UObject/ObjectMacros.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "IOculusXRHMDModule.h" +#include "OculusXRFunctionLibrary.generated.h" + +namespace OculusXRHMD +{ + class FOculusXRHMD; +} + +/* Tracked device types corresponding to ovrTrackedDeviceType enum*/ +UENUM(BlueprintType) +enum class EOculusXRTrackedDeviceType : uint8 +{ + None UMETA(DisplayName = "No Devices"), + HMD UMETA(DisplayName = "HMD"), + LTouch UMETA(DisplayName = "Left Hand"), + RTouch UMETA(DisplayName = "Right Hand"), + Touch UMETA(DisplayName = "All Hands"), + DeviceObjectZero UMETA(DisplayName = "DeviceObject Zero"), + All UMETA(DisplayName = "All Devices") +}; + +USTRUCT(BlueprintType, meta = (DisplayName = "HMD User Profile Data Field")) +struct FOculusXRHmdUserProfileField +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + FString FieldName; + + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + FString FieldValue; + + FOculusXRHmdUserProfileField() {} + FOculusXRHmdUserProfileField(const FString& Name, const FString& Value) : + FieldName(Name), FieldValue(Value) {} +}; + +USTRUCT(BlueprintType, meta = (DisplayName = "HMD User Profile Data")) +struct FOculusXRHmdUserProfile +{ + GENERATED_USTRUCT_BODY() + + /** Name of the user's profile. */ + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + FString Name; + + /** Gender of the user ("male", "female", etc). */ + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + FString Gender; + + /** Height of the player, in meters */ + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + float PlayerHeight; + + /** Height of the player, in meters */ + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + float EyeHeight; + + /** Interpupillary distance of the player, in meters */ + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + float IPD; + + /** Neck-to-eye distance, in meters. X - horizontal, Y - vertical. */ + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + FVector2D NeckToEyeDistance; + + UPROPERTY(BlueprintReadWrite, Category = "Input|HeadMountedDisplay") + TArray<FOculusXRHmdUserProfileField> ExtraFields; + + FOculusXRHmdUserProfile() : + PlayerHeight(0.f), EyeHeight(0.f), IPD(0.f), NeckToEyeDistance(FVector2D::ZeroVector) {} +}; + +UENUM(BlueprintType) +enum class EOculusXRFoveatedRenderingMethod : uint8 +{ + FixedFoveatedRendering = 0, + EyeTrackedFoveatedRendering = 1, +}; + +UENUM(BlueprintType) +enum class EOculusXRFoveatedRenderingLevel : uint8 +{ + Off = 0, + Low = 1, + Medium = 2, + High = 3, + // High foveation setting with more detail toward the bottom of the view and more foveation near the top + HighTop = 4 +}; + +/* Guardian boundary types*/ +UENUM(BlueprintType) +enum class EOculusXRBoundaryType : uint8 +{ + Boundary_Outer UMETA(DisplayName = "Outer Boundary"), + Boundary_PlayArea UMETA(DisplayName = "Play Area"), +}; + +UENUM(BlueprintType) +enum class EOculusXRColorSpace : uint8 +{ + /// The default value from GetHmdColorSpace until SetClientColorDesc is called. Only valid on PC, and will be remapped to Quest on Mobile + Unknown = 0, + /// No color correction, not recommended for production use. See documentation for more info + Unmanaged = 1, + /// Color space for standardized color across all Oculus HMDs with D65 white point + Rec_2020 = 2, + /// Rec. 709 is used on Oculus Go and shares the same primary color coordinates as sRGB + Rec_709 = 3, + /// Oculus Rift CV1 uses a unique color space, see documentation for more info + Rift_CV1 = 4 UMETA(DisplayName = "Rift CV1"), + /// Oculus Rift S uses a unique color space, see documentation for more info + Rift_S = 5, + /// Oculus Quest's native color space is slightly different than Rift CV1 + Quest = 6 UMETA(DisplayName = "Quest 1"), + /// DCI-P3 color space. See documentation for more details + P3 = 7 UMETA(DisplayName = "P3 (Recommended)"), + /// Similar to sRGB but with deeper greens using D65 white point + Adobe_RGB = 8, +}; + +UENUM(BlueprintType) +enum class EOculusXRHandTrackingSupport : uint8 +{ + ControllersOnly, + ControllersAndHands, + HandsOnly, +}; + +UENUM(BlueprintType) +enum class EOculusXRHandTrackingFrequency : uint8 +{ + LOW, + HIGH, + MAX, +}; + +UENUM(BlueprintType) +enum class EOculusXRProcessorPerformanceLevel : uint8 +{ + PowerSavings = 0 UMETA(DisplayName = "PowerSavings", ToolTip = "Usually used in non-XR section (head-locked / static screen), during which power savings are to be prioritized"), + SustainedLow = 1 UMETA(DisplayName = "SustainedLow", ToolTip = "App enters a low and stable complexity section, during which reducing power is more important than occasional late rendering frames"), + SustainedHigh = 2 UMETA(DisplayName = "SustainedHigh", ToolTip = "Let XR Runtime to perform consistent XR compositing and frame rendering within a thermally sustainable range"), + Boost = 3 UMETA(DisplayName = "Boost(*)", ToolTip = "Allow XR Runtime to step up beyond the thermally sustainable range for short period. (Currently equivalent to SustainedHigh and not recommended to be used on Quest)") +}; + +UENUM(BlueprintType) +enum class EOculusXRDeviceType : uint8 +{ + //mobile HMDs + OculusMobile_Deprecated0 = 0, + OculusQuest, + OculusQuest2, + MetaQuestPro, + + //PC HMDs + Rift = 100, + Rift_S, + Quest_Link, + Quest2_Link, + MetaQuestProLink, + + //default + OculusUnknown = 200, +}; + +UENUM(BlueprintType) +enum class EOculusXRControllerType : uint8 +{ + None = 0, + MetaQuestTouch = 1, + MetaQuestTouchPro = 2, + Unknown = 0x7f, +}; + +UENUM(BlueprintType) +enum class EOculusXRXrApi : uint8 +{ + OVRPluginOpenXR = 0 UMETA(DisplayName = "Oculus OVRPlugin + OpenXR backend (current recommended)", ToolTip = "Oculus plugin integration using OpenXR backend on both Mobile and PC. All new features will ship on backend for the forseeable future."), + + NativeOpenXR = 1 UMETA(DisplayName = "Epic Native OpenXR with Oculus vendor extensions", ToolTip = "Disable Legacy Oculus in favor of the native OpenXR implementation, with Oculus vendor extensions. Must enable the OpenXR plugin. This will be where Epic focuses XR development going forward. Oculus OpenXR extensions may be moved into a separate plugin (or plugins) in the future to improve modularity. The features supported by OpenXR are listed in the OpenXR specification on khronos.org, and the features supported by a given runtime can be verified with the \"OpenXR Explorer\" application on GitHub."), +}; + +/* +* Information about relationships between a triggered boundary (EOculusXRBoundaryType::Boundary_Outer or +* EOculusXRBoundaryType::Boundary_PlayArea) and a device or point in the world. +* All dimensions, points, and vectors are returned in Unreal world coordinate space. +*/ +USTRUCT(BlueprintType) +struct FOculusXRGuardianTestResult +{ + GENERATED_BODY() + + /** Is there a triggering interaction between the device/point and specified boundary? */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Boundary Test Result") + bool IsTriggering = false; + + /** Device type triggering boundary (EOculusXRTrackedDeviceType::None if BoundaryTestResult corresponds to a point rather than a device) */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Boundary Test Result") + EOculusXRTrackedDeviceType DeviceType = EOculusXRTrackedDeviceType::None; + + /** Distance of device/point to surface of boundary specified by BoundaryType */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Boundary Test Result") + float ClosestDistance = 0.0f; + + /** Closest point on surface corresponding to specified boundary */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Boundary Test Result") + FVector ClosestPoint = FVector(0.0f); + + /** Normal of closest point */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Boundary Test Result") + FVector ClosestPointNormal = FVector(0.0f, 0.0f, 1.0f); +}; + +UENUM() +enum class EOculusXRControllerPoseAlignment : uint8 +{ + Default = 0 UMETA(ToolTip = "Default pose alignment used in all versions of the Meta XR plugin. Recommended pose for compatibility with previous assets designed for the Meta XR plugin."), + + Grip = 1 UMETA(ToolTip = "Grip pose alignment as defined by OpenXR. Use this for cross-plugin compatibility with assets designed for the native OpenXR grip pose."), + + Aim = 2 UMETA(ToolTip = "Aim pose alignment as defined by OpenXR. Use this for cross-plugin compatibility with assets designed for the native OpenXR aim pose."), +}; + +UCLASS() +class OCULUSXRHMD_API UOculusXRFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + + /** + * Grabs the current orientation and position for the HMD. If positional tracking is not available, DevicePosition will be a zero vector + * + * @param DeviceRotation (out) The device's current rotation + * @param DevicePosition (out) The device's current position, in its own tracking space + * @param NeckPosition (out) The estimated neck position, calculated using NeckToEye vector from User Profile. Same coordinate space as DevicePosition. + * @param bUseOrienationForPlayerCamera (in) Should be set to 'true' if the orientation is going to be used to update orientation of the camera manually. + * @param bUsePositionForPlayerCamera (in) Should be set to 'true' if the position is going to be used to update position of the camera manually. + * @param PositionScale (in) The 3D scale that will be applied to position. + */ + UFUNCTION(BlueprintPure, Category="OculusLibrary") + static void GetPose(FRotator& DeviceRotation, FVector& DevicePosition, FVector& NeckPosition, bool bUseOrienationForPlayerCamera = false, bool bUsePositionForPlayerCamera = false, const FVector PositionScale = FVector::ZeroVector); + + /** + * Reports raw sensor data. If HMD doesn't support any of the parameters then it will be set to zero. + * + * @param AngularAcceleration (out) Angular acceleration in radians per second per second. + * @param LinearAcceleration (out) Acceleration in meters per second per second. + * @param AngularVelocity (out) Angular velocity in radians per second. + * @param LinearVelocity (out) Velocity in meters per second. + * @param TimeInSeconds (out) Time when the reported IMU reading took place, in seconds. + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static void GetRawSensorData(FVector& AngularAcceleration, FVector& LinearAcceleration, FVector& AngularVelocity, FVector& LinearVelocity, float& TimeInSeconds, EOculusXRTrackedDeviceType DeviceType = EOculusXRTrackedDeviceType::HMD); + + /** + * Returns if the device is currently tracked by the runtime or not. + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static bool IsDeviceTracked(EOculusXRTrackedDeviceType DeviceType); + + /** + * Set the CPU and GPU levels as hints to the Oculus device (Deprecated). + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary", meta=(DeprecatedFunction, + DeprecatedMessage="Deprecated. Please use Get/SetSuggestedCpuAndGpuPerformanceLevels instead")) + static void SetCPUAndGPULevels(int CPULevel, int GPULevel); + + /** + * Get the suggested CPU and GPU levels to the Oculus device. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void GetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel& CpuPerfLevel, EOculusXRProcessorPerformanceLevel& GpuPerfLevel); + + /** + * Set the suggested CPU and GPU levels to the Oculus device. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void SetSuggestedCpuAndGpuPerformanceLevels(EOculusXRProcessorPerformanceLevel CpuPerfLevel, EOculusXRProcessorPerformanceLevel GpuPerfLevel); + + /** + * Returns current user profile. + * + * @param Profile (out) Structure to hold current user profile. + * @return (boolean) True, if user profile was acquired. + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static bool GetUserProfile(FOculusXRHmdUserProfile& Profile); + + /** + * Sets 'base rotation' - the rotation that will be subtracted from + * the actual HMD orientation. + * Sets base position offset (in meters). The base position offset is the distance from the physical (0, 0, 0) position + * to current HMD position (bringing the (0, 0, 0) point to the current HMD position) + * Note, this vector is set by ResetPosition call; use this method with care. + * The axis of the vector are the same as in Unreal: X - forward, Y - right, Z - up. + * + * @param Rotation (in) Rotator object with base rotation + * @param BaseOffsetInMeters (in) the vector to be set as base offset, in meters. + * @param Options (in) specifies either position, orientation or both should be set. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void SetBaseRotationAndBaseOffsetInMeters(FRotator Rotation, FVector BaseOffsetInMeters, EOrientPositionSelector::Type Options); + + /** + * Returns current base rotation and base offset. + * The base offset is currently used base position offset, previously set by the + * ResetPosition or SetBasePositionOffset calls. It represents a vector that translates the HMD's position + * into (0,0,0) point, in meters. + * The axis of the vector are the same as in Unreal: X - forward, Y - right, Z - up. + * + * @param OutRotation (out) Rotator object with base rotation + * @param OutBaseOffsetInMeters (out) base position offset, vector, in meters. + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static void GetBaseRotationAndBaseOffsetInMeters(FRotator& OutRotation, FVector& OutBaseOffsetInMeters); + + /** + * Scales the HMD position that gets added to the virtual camera position. + * + * @param PosScale3D (in) the scale to apply to the HMD position. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary", meta = (DeprecatedFunction, DeprecationMessage = "This feature is no longer supported.")) + static void SetPositionScale3D(FVector PosScale3D) { } + + /** + * Sets 'base rotation' - the rotation that will be subtracted from + * the actual HMD orientation. + * The position offset might be added to current HMD position, + * effectively moving the virtual camera by the specified offset. The addition + * occurs after the HMD orientation and position are applied. + * + * @param BaseRot (in) Rotator object with base rotation + * @param PosOffset (in) the vector to be added to HMD position. + * @param Options (in) specifies either position, orientation or both should be set. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary", meta = (DeprecatedFunction, DeprecationMessage = "A hack, proper camera positioning should be used")) + static void SetBaseRotationAndPositionOffset(FRotator BaseRot, FVector PosOffset, EOrientPositionSelector::Type Options); + + /** + * Returns current base rotation and position offset. + * + * @param OutRot (out) Rotator object with base rotation + * @param OutPosOffset (out) the vector with previously set position offset. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary", meta = (DeprecatedFunction, DeprecationMessage = "A hack, proper camera positioning should be used")) + static void GetBaseRotationAndPositionOffset(FRotator& OutRot, FVector& OutPosOffset); + + /** + * Adds loading splash screen with parameters + * + * @param Texture (in) A texture asset to be used for the splash. + * @param TranslationInMeters (in) Initial translation of the center of the splash screen (in meters). + * @param Rotation (in) Initial rotation of the splash screen, with the origin at the center of the splash screen. + * @param SizeInMeters (in) Size, in meters, of the quad with the splash screen. + * @param DeltaRotation (in) Incremental rotation, that is added each 2nd frame to the quad transform. The quad is rotated around the center of the quad. + * @param bClearBeforeAdd (in) If true, clears splashes before adding a new one. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary", meta = (DeprecatedFunction, DeprecationMessage = "Use Add Loading Screen Splash from the Head Mounted Display Loading Screen functions instead.")) + static void AddLoadingSplashScreen(class UTexture2D* Texture, FVector TranslationInMeters, FRotator Rotation, FVector2D SizeInMeters = FVector2D(1.0f, 1.0f), FRotator DeltaRotation = FRotator::ZeroRotator, bool bClearBeforeAdd = false); + + /** + * Removes all the splash screens. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary", meta = (DeprecatedFunction, DeprecationMessage = "Use Clear Loading Screen Splashes from the Head Mounted Display Loading Screen functions instead.")) + static void ClearLoadingSplashScreens(); + + /** + * Returns true, if the app has input focus. + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static bool HasInputFocus(); + + /** + * Returns true, if the system overlay is present. + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static bool HasSystemOverlayPresent(); + + /** + * Returns the GPU utilization availability and value + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static void GetGPUUtilization(bool& IsGPUAvailable, float& GPUUtilization); + + /** + * Returns the GPU frame time on supported mobile platforms (Go for now) + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static float GetGPUFrameTime(); + + /** + * Returns the foveated rendering method currently being used + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static EOculusXRFoveatedRenderingMethod GetFoveatedRenderingMethod(); + + /** + * Set the requested foveated rendering method + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void SetFoveatedRenderingMethod(EOculusXRFoveatedRenderingMethod Method); + + /** + * Returns the current multiresolution level + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static EOculusXRFoveatedRenderingLevel GetFoveatedRenderingLevel(); + + /** + * Set the requested foveated rendering level for the next frame, and whether FFR's level is now dynamic or not. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void SetFoveatedRenderingLevel(EOculusXRFoveatedRenderingLevel level, bool isDynamic); + + /** + * Returns whether eye-tracked foveated rendering is supported or not + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static bool GetEyeTrackedFoveatedRenderingSupported(); + + /** + * Returns the current device's name + */ + UE_DEPRECATED(4.22, "UOculusXRFunctionLibrary::GetDeviceName has been deprecated and no longer functions as before. Please use the enum-based GetDeviceType instead.") + UFUNCTION(BlueprintPure, Category = "OculusLibrary", meta = (DeprecatedFunction, DeprecationMessage = "UOculusXRFunctionLibrary::GetDeviceName has been deprecated and no longer functions as before. Please use the enum-based GetDeviceType instead.")) + static FString GetDeviceName(); + + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static EOculusXRDeviceType GetDeviceType(); + + /** + * Returns the current controller's type + * @param deviceHand (in) The hand to get the position from + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static EOculusXRControllerType GetControllerType(EControllerHand deviceHand); + + /** + * Returns the current available frequencies + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static TArray<float> GetAvailableDisplayFrequencies(); + + /** + * Returns the current display frequency + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static float GetCurrentDisplayFrequency(); + + /** + * Sets the requested display frequency + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void SetDisplayFrequency(float RequestedFrequency); + + /** + * Enables/disables positional tracking on devices that support it. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void EnablePositionTracking(bool bPositionTracking); + + /** + * Enables/disables orientation tracking on devices that support it. + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void EnableOrientationTracking(bool bOrientationTracking); + + /** + * Set the Color Scale/Offset + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void SetColorScaleAndOffset(FLinearColor ColorScale, FLinearColor ColorOffset, bool bApplyToAllLayers = false); + + /** + * Returns true if system headset is in 3dof mode + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static bool GetSystemHmd3DofModeEnabled(); + + /** + * Returns the color space of the target HMD + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary") + static EOculusXRColorSpace GetHmdColorDesc(); + + /** + * Sets the target HMD to do color space correction to a specific color space + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void SetClientColorDesc(EOculusXRColorSpace ColorSpace); + + + /** + * Turns on or off local dimming + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static void SetLocalDimmingOn(bool LocalDimmingOn); + + /** + * Checks if passthrough is supported + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static bool IsPassthroughSupported(); + + /** + * Checks if color passthrough is supported + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary") + static bool IsColorPassthroughSupported(); + + /** + * Returns IStereoLayers interface to work with overlays. + */ + static class IStereoLayers* GetStereoLayers(); + + /* GUARDIAN API */ + /** + * Returns true if the Guardian Outer Boundary is being displayed + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|Guardian") + static bool IsGuardianDisplayed(); + + /* GUARDIAN API */ + /** + * Returns true if the Guardian has been set up by the user, false if the user is in "seated" mode and has not set up a play space. + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|Guardian") + static bool IsGuardianConfigured(); + + /** + * Returns the list of points in UE world space of the requested Boundary Type + * @param BoundaryType (in) An enum representing the boundary type requested, either Outer Boundary (exact guardian bounds) or PlayArea (rectangle inside the Outer Boundary) + * @param UsePawnSpace (in) Boolean indicating to return the points in world space or pawn space + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|Guardian") + static TArray<FVector> GetGuardianPoints(EOculusXRBoundaryType BoundaryType, bool UsePawnSpace = false); + + /** + * Returns the dimensions in UE world space of the requested Boundary Type + * @param BoundaryType (in) An enum representing the boundary type requested, either Outer Boundary (exact guardian bounds) or PlayArea (rectangle inside the Outer Boundary) + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|Guardian") + static FVector GetGuardianDimensions(EOculusXRBoundaryType BoundaryType); + + /** + * Returns the transform of the play area rectangle, defining its position, rotation and scale to apply to a unit cube to match it with the play area. + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|Guardian") + static FTransform GetPlayAreaTransform(); + + /** + * Get the intersection result between a UE4 coordinate and a guardian boundary + * @param Point (in) Point in UE space to test against guardian boundaries + * @param BoundaryType (in) An enum representing the boundary type requested, either Outer Boundary (exact guardian bounds) or PlayArea (rectangle inside the Outer Boundary) + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Guardian") + static FOculusXRGuardianTestResult GetPointGuardianIntersection(const FVector Point, EOculusXRBoundaryType BoundaryType); + + /** + * Get the intersection result between a tracked device (HMD or controllers) and a guardian boundary + * @param DeviceType (in) Tracked Device type to test against guardian boundaries + * @param BoundaryType (in) An enum representing the boundary type requested, either Outer Boundary (exact guardian bounds) or PlayArea (rectangle inside the Outer Boundary) + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Guardian") + static FOculusXRGuardianTestResult GetNodeGuardianIntersection(EOculusXRTrackedDeviceType DeviceType, EOculusXRBoundaryType BoundaryType); + + /** + * Forces the runtime to render guardian at all times or not + * @param GuardianVisible (in) True will display guardian, False will hide it + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Guardian") + static void SetGuardianVisibility(bool GuardianVisible); + + /** When player triggers the Guardian boundary */ + DECLARE_MULTICAST_DELEGATE_OneParam(FOculusGuardianTriggeredEvent, FOculusXRGuardianTestResult); + + /** When player returns within outer bounds */ + DECLARE_MULTICAST_DELEGATE(FOculusGuardianReturnedEvent); + + /** + * For outer boundary only. Devs can bind delegates via something like: BoundaryComponent->OnOuterBoundaryTriggered.AddDynamic(this, &UCameraActor::PauseGameForBoundarySystem) where + * PauseGameForBoundarySystem() takes a TArray<FBoundaryTestResult> parameter. + */ + //UPROPERTY(BlueprintAssignable, Category = "Input|OculusLibrary|Guardian") + //static FOculusGuardianTriggeredEvent OnGuardianTriggered; + + /** For outer boundary only. Devs can bind delegates via something like: BoundaryComponent->OnOuterBoundaryReturned.AddDynamic(this, &UCameraActor::ResumeGameForBoundarySystem) */ + //UPROPERTY(BlueprintAssignable, Category = "OculusLibrary|Guardian") + //FOculusGuardianReturnedEvent OnGuardianReturned; + +protected: + static class OculusXRHMD::FOculusXRHMD* GetOculusXRHMD(); +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRHMDRuntimeSettings.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRHMDRuntimeSettings.h new file mode 100644 index 0000000000000000000000000000000000000000..6ebf7639e8295490077c02a523908ae06b3a3b80 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRHMDRuntimeSettings.h @@ -0,0 +1,163 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/Object.h" +#include "OculusXRHMDTypes.h" +#include "OculusXRFunctionLibrary.h" +#include "OculusXRHMDRuntimeSettings.generated.h" + +UENUM() +enum class EOculusXRSupportedDevices : uint8 +{ + Quest UMETA(DisplayName = "Meta Quest"), + Quest2 UMETA(DisplayName = "Meta Quest 2"), + QuestPro UMETA(DisplayName = "Meta Quest Pro"), +}; + +/** +* Implements the settings for the OculusVR plugin. +*/ +UCLASS(config = Engine, defaultconfig) +class OCULUSXRHMD_API UOculusXRHMDRuntimeSettings : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + /** Whether the Splash screen is enabled. */ + UPROPERTY(config, EditAnywhere, Category = "Engine SplashScreen") + bool bAutoEnabled; + + /** An array of splash screen descriptors listing textures to show and their positions. */ + UPROPERTY(config, EditAnywhere, Category = "Engine SplashScreen") + TArray<FOculusXRSplashDesc> SplashDescs; + + /** This selects the XR API that the engine will use. If unsure, OVRPlugin OpenXR is the recommended API. */ + UPROPERTY(config, EditAnywhere, Category = General, meta = (DisplayName = "XR API", ConfigRestartRequired = true)) + EOculusXRXrApi XrApi; + + /** The target color space */ + UPROPERTY(config, EditAnywhere, Category = General) + EOculusXRColorSpace ColorSpace; + + /** Whether the controller hand poses align to the Meta XR pose definitions or the OpenXR pose definitions */ + UPROPERTY(config, EditAnywhere, Category = General, meta = (EditCondition = "XrApi != EOculusXRXrApi::NativeOpenXR")) + EOculusXRControllerPoseAlignment ControllerPoseAlignment; + + /** Whether Dash is supported by the app, which will keep the app in foreground when the User presses the oculus button (needs the app to handle input focus loss!) */ + UPROPERTY(config, EditAnywhere, Category = PC) + bool bSupportsDash; + + /** Whether the app's depth buffer is shared with the Rift Compositor, for layer (including Dash) compositing, PTW, and potentially more. */ + UPROPERTY(config, EditAnywhere, Category = PC) + bool bCompositesDepth; + + /** Computes mipmaps for the eye buffers every frame, for a higher quality distortion */ + UPROPERTY(config, EditAnywhere, Category = PC) + bool bHQDistortion; + + /** Minimum allowed pixel density. */ + UPROPERTY(config, EditAnywhere, Category = PC) + float PixelDensityMin; + + /** Maximum allowed pixel density. */ + UPROPERTY(config, EditAnywhere, Category = PC) + float PixelDensityMax; + + /** Default CPU level controlling CPU frequency on the mobile device */ + UPROPERTY(config, meta = (DeprecatedProperty, + DeprecationMessage = "Use Blueprint Function Get/SetSuggestedCpuAndGpuPerformanceLevels instead.")) + int CPULevel_DEPRECATED; + + /** Default GPU level controlling GPU frequency on the mobile device */ + UPROPERTY(config, meta = (DeprecatedProperty, + DeprecationMessage = "Use Blueprint Function Get/SetSuggestedCpuAndGpuPerformanceLevels instead.")) + int GPULevel_DEPRECATED; + + /** Select supported Meta Quest Devices */ + UPROPERTY(config, EditAnywhere, Category = Mobile, meta = (DisplayName = "Supported Meta Quest devices")) + TArray<EOculusXRSupportedDevices> SupportedDevices; + + /** Suggested CPU perf level when application starts on Oculus Quest */ + UPROPERTY(config, EditAnywhere, Category = Mobile) + EOculusXRProcessorPerformanceLevel SuggestedCpuPerfLevel; + + /** Suggested GPU perf level when application starts on Oculus Quest */ + UPROPERTY(config, EditAnywhere, Category = Mobile) + EOculusXRProcessorPerformanceLevel SuggestedGpuPerfLevel; + + /** Foveated rendering method */ + UPROPERTY(config, EditAnywhere, Category = "Mobile|Foveated Rendering", meta = (EditCondition = "XrApi == EOculusXRXrApi::OVRPluginOpenXR")) + EOculusXRFoveatedRenderingMethod FoveatedRenderingMethod; + + /** Foveated rendering level */ + UPROPERTY(config, EditAnywhere, Category = "Mobile|Foveated Rendering", meta = (EditCondition = "XrApi != EOculusXRXrApi::NativeOpenXR")) + EOculusXRFoveatedRenderingLevel FoveatedRenderingLevel; + + /** Whether foveated rendering levels will change dynamically based on performance headroom or not (up to the set Foveation Level) */ + UPROPERTY(config, EditAnywhere, Category = "Mobile|Foveated Rendering", meta = (EditCondition = "XrApi != EOculusXRXrApi::NativeOpenXR")) + bool bDynamicFoveatedRendering; + + /** Whether eye tracked foveated rendering can be used with the app. */ + UPROPERTY(config, EditAnywhere, Category = "Mobile|Foveated Rendering", meta = (EditCondition = "XrApi == EOculusXRXrApi::OVRPluginOpenXR")) + bool bSupportEyeTrackedFoveatedRendering; + + /** If enabled the app will be focus aware. This will keep the app in foreground when the User presses the oculus button (needs the app to handle input focus loss!) */ + UPROPERTY(config, EditAnywhere, Category = Mobile, meta = (EditCondition = "false")) + bool bFocusAware; + + /** [Experimental]Enable Late latching for reducing HMD and controller latency, improve tracking prediction quality, multiview and vulkan must be enabled for this feature. */ + UPROPERTY(config, EditAnywhere, Category = Mobile) + bool bLateLatching; + + /** If enabled the app will use the Oculus system keyboard for input fields. This requires that the app be focus aware. */ + UPROPERTY(config, EditAnywhere, Category = Mobile) + bool bRequiresSystemKeyboard; + + /** Whether controllers and/or hands can be used with the app */ + UPROPERTY(config, EditAnywhere, Category = Mobile) + EOculusXRHandTrackingSupport HandTrackingSupport; + + /** Note that a higher tracking frequency will reserve some performance headroom from the application's budget. */ + UPROPERTY(config, EditAnywhere, Category = Mobile) + EOculusXRHandTrackingFrequency HandTrackingFrequency; + + /** Whether passthrough functionality can be used with the app */ + UPROPERTY(config, EditAnywhere, Category = Mobile, meta = (DisplayName = "Passthrough Enabled")) + bool bInsightPassthroughEnabled; + + /** Whether Scene and Spatial Anchors can be used with the app */ + UPROPERTY(config, EditAnywhere, Category = Mobile, meta = (DisplayName = "Anchor Support")) + bool bAnchorSupportEnabled; + + /** Whether body tracking functionality can be used with the app */ + UPROPERTY(config, EditAnywhere, Category = Mobile, meta = (DisplayName = "Body Tracking Enabled", EditCondition = "XrApi == EOculusXRXrApi::OVRPluginOpenXR")) + bool bBodyTrackingEnabled; + + /** Whether eye tracking functionality can be used with the app */ + UPROPERTY(config, EditAnywhere, Category = Mobile, meta = (DisplayName = "Eye Tracking Enabled", EditCondition = "XrApi == EOculusXRXrApi::OVRPluginOpenXR")) + bool bEyeTrackingEnabled; + + /** Whether face tracking functionality can be used with the app */ + UPROPERTY(config, EditAnywhere, Category = Mobile, meta = (DisplayName = "Face Tracking Enabled", EditCondition = "XrApi == EOculusXRXrApi::OVRPluginOpenXR")) + bool bFaceTrackingEnabled; + + /** On supported Oculus mobile platforms, copy compiled .so directly to device. Allows updating compiled code without rebuilding and installing an APK. */ + UPROPERTY(config, EditAnywhere, Category = Mobile, meta = (DisplayName = "Deploy compiled .so directly to device")) + bool bDeploySoToDevice; + + /** Whether experimental features listed below can be used with the app. */ + UPROPERTY(config, EditAnywhere, Category = Experimental) + bool bSupportExperimentalFeatures; + +private: +#if WITH_EDITOR + virtual bool CanEditChange(const FProperty* InProperty) const override; + virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; +#endif // WITH_EDITOR + + void LoadFromIni(); + void RenameProperties(); +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRHMDTypes.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRHMDTypes.h new file mode 100644 index 0000000000000000000000000000000000000000..1a50076a94bf7d3d33caf681200510cfb619e820 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRHMDTypes.h @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "RHI.h" +#include "RHIResources.h" +#include "Engine/Texture2D.h" +#include "UObject/SoftObjectPath.h" +#include "OculusXRHMDTypes.generated.h" + +USTRUCT() +struct FOculusXRSplashDesc +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY(config, EditAnywhere, Category = Settings, meta = ( + AllowedClasses = "Texture", + ToolTip = "Texture to display")) + FSoftObjectPath TexturePath; + + UPROPERTY(config, EditAnywhere, Category = Settings, meta = ( + ToolTip = "transform of center of quad (meters).")) + FTransform TransformInMeters; + + UPROPERTY(config, EditAnywhere, Category = Settings, meta = ( + ToolTip = "Dimensions in meters.")) + FVector2D QuadSizeInMeters; + + UPROPERTY(config, EditAnywhere, Category = Settings, meta = ( + ToolTip = "A delta rotation that will be added each rendering frame (half rate of full vsync).")) + FQuat DeltaRotation; + + UPROPERTY(config, EditAnywhere, Category = Settings, meta = ( + ToolTip = "Texture offset amount from the top left corner.")) + FVector2D TextureOffset; + + UPROPERTY(config, EditAnywhere, Category = Settings, meta = ( + ToolTip = "Texture scale.")) + FVector2D TextureScale; + + UPROPERTY(config, EditAnywhere, Category = Settings, meta = ( + ToolTip = "Whether the splash layer uses it's alpha channel.")) + bool bNoAlphaChannel; + + // Runtime data + UTexture* LoadingTexture; + FTextureRHIRef LoadedTexture; + bool bIsDynamic; + + FOculusXRSplashDesc() + : TransformInMeters(FVector(4.0f, 0.f, 0.f)) + , QuadSizeInMeters(3.f, 3.f) + , DeltaRotation(FQuat::Identity) + , TextureOffset(0.0f, 0.0f) + , TextureScale(1.0f, 1.0f) + , bNoAlphaChannel(false) + , LoadingTexture(nullptr) + , LoadedTexture(nullptr) + , bIsDynamic(false) + { + } + + bool operator==(const FOculusXRSplashDesc& d) const + { + return TexturePath == d.TexturePath && + TransformInMeters.Equals(d.TransformInMeters) && + QuadSizeInMeters == d.QuadSizeInMeters && DeltaRotation.Equals(d.DeltaRotation) && + TextureOffset == d.TextureOffset && TextureScale == d.TextureScale && + bNoAlphaChannel == d.bNoAlphaChannel && + LoadingTexture == d.LoadingTexture && LoadedTexture == d.LoadedTexture && bIsDynamic == d.bIsDynamic; + } +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughLayerComponent.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughLayerComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..e41467a36162abe16c754c1c22d5d2d4452abb31 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughLayerComponent.h @@ -0,0 +1,198 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +// OculusEventComponent.h: Component to handle receiving events from Oculus HMDs + +#pragma once + +#include "CoreMinimal.h" +#include "Engine/StaticMeshActor.h" +#include "UObject/ObjectMacros.h" +#include "Components/StereoLayerComponent.h" +#include "OculusXRPassthroughLayerShapes.h" +#include "OculusXRPassthroughLayerComponent.generated.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogOculusPassthrough, Log, All); + +UCLASS(meta = (DisplayName = "Passthrough Layer Base")) +class OCULUSXRHMD_API UOculusXRPassthroughLayerBase : public UStereoLayerShape +{ + GENERATED_BODY() + +public: + /** Ordering of passthrough layer in relation to scene rendering */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", DisplayName = "Layer Placement") + TEnumAsByte<enum EOculusXRPassthroughLayerOrder> LayerOrder; + + /** Opacity of the (main) passthrough texture. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (UIMin = 0.0, UIMax = 1.0, ClampMin = 0.0, ClampMax = 1.0)) + float TextureOpacityFactor = 1.0f; + + /** Enable edge color */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties") + bool bEnableEdgeColor = false; + + /** Color of the passthrough edge rendering effect. */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties") + FLinearColor EdgeColor; + + /** Enable color mapping */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties") + bool bEnableColorMap = false; + + /** Type of colormapping to perform */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap", EditConditionHides)) + TEnumAsByte<enum EOculusXRColorMapType> ColorMapType; + + /** Whether to use color map curve or gradient*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap && ColorMapType == 1", EditConditionHides)) + bool bUseColorMapCurve = false; + + /** Passthrough color mapping gradient converts grayscale to color*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap && bUseColorMapCurve && ColorMapType == 1", EditConditionHides)) + UCurveLinearColor* ColorMapCurve; + + /** Contrast setting for color mapping*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "-1", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType > 0", EditConditionHides)) + float Contrast = 0.0f; + + /** Brightness setting for color mapping*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "-1", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType > 0", EditConditionHides)) + float Brightness = 0.0f; + + /** Posterize setting for grayscale and grayscale to color mapping*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "0", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType > 0 && ColorMapType < 3", EditConditionHides)) + float Posterize = 0.0f; + + /** Saturation setting for color adjustment mapping*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (ClampMin = "-1", ClampMax = "1", EditCondition = "bEnableColorMap && ColorMapType == 3", EditConditionHides)) + float Saturation = 0.0f; + + /** Color value that will be multiplied to the current color map*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap", EditConditionHides)) + FLinearColor ColorScale = FLinearColor::White; + + /** Color value that will be added to the current color map*/ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "Passthrough Properties", meta = (EditCondition = "bEnableColorMap", EditConditionHides)) + FLinearColor ColorOffset = FLinearColor::Black; + + UFUNCTION(BlueprintCallable, Category ="Components|Stereo Layer") + void SetTextureOpacity(float InOpacity); + + UFUNCTION(BlueprintCallable, Category ="Components|Stereo Layer") + void EnableEdgeColor(bool bInEnableEdgeColor); + + UFUNCTION(BlueprintCallable, Category ="Components|Stereo Layer") + void EnableColorMap(bool bInEnableColorMap); + + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + void EnableColorMapCurve(bool bInEnableColorMapCurve); + + UFUNCTION(BlueprintCallable, Category ="Components|Stereo Layer") + void SetEdgeRenderingColor(FLinearColor InEdgeColor); + + /** Set color map controls for grayscale and grayscale to rgb color mapping*/ + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + void SetColorMapControls(float InContrast = 0, float InBrightness = 0, float InPosterize = 0); + + /** Set color map controls for color adjustment color mapping*/ + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + void SetBrightnessContrastSaturation(float InContrast = 0, float InBrightness = 0, float InSaturation = 0); + + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + void SetColorScaleAndOffset(FLinearColor InColorScale = FLinearColor::White, FLinearColor InColorOffset = FLinearColor::Black); + + /** Set color curve that will be added to the color map in grayscale modes --> will be converted into a gradient*/ + UFUNCTION(BlueprintCallable, Category ="Components|Stereo Layer") + void SetColorMapCurve(UCurveLinearColor* InColorMapCurve); + + UFUNCTION(BlueprintCallable, Category ="Components|Stereo Layer") + void SetColorMapType(EOculusXRColorMapType InColorMapType); + + /** Set color map array directly instead through a color curve*/ + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + void SetColorArray(const TArray<FLinearColor>& InColorArray); + + UFUNCTION(BlueprintCallable, Category = "Components|Stereo Layer") + void ClearColorMap(); + + UFUNCTION(BlueprintCallable, Category = "Passthrough Properties") + void SetLayerPlacement(EOculusXRPassthroughLayerOrder InLayerOrder); + +protected: + TArray<FLinearColor> ColorArray; + TArray<FLinearColor> NeutralColorArray; + + TArray<FLinearColor> GenerateColorArrayFromColorCurve(const UCurveLinearColor* InColorMapCurve) const; + TArray<FLinearColor> GetOrGenerateNeutralColorArray(); + TArray<FLinearColor> GenerateColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve); + TArray<FLinearColor> GetColorArray(bool bInUseColorMapCurve, const UCurveLinearColor* InColorMapCurve); +private: + +}; + +/* Reconstructed Passthrough Layer*/ +UCLASS(meta = (DisplayName = "Reconstructed Passthrough Layer")) +class OCULUSXRHMD_API UOculusXRStereoLayerShapeReconstructed : public UOculusXRPassthroughLayerBase +{ + GENERATED_BODY() +public: + virtual void ApplyShape(IStereoLayers::FLayerDesc& LayerDesc) override; + +}; + +/* User Defined Passthrough Layer*/ +UCLASS(meta = (DisplayName = "User Defined Passthrough Layer")) +class OCULUSXRHMD_API UOculusXRStereoLayerShapeUserDefined : public UOculusXRPassthroughLayerBase +{ + GENERATED_BODY() +public: + void AddGeometry(const FString& MeshName, OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, FTransform Transform, bool bUpdateTransform); + void RemoveGeometry(const FString& MeshName); + + virtual void ApplyShape(IStereoLayers::FLayerDesc& LayerDesc) override; + TArray<FUserDefinedGeometryDesc>& GetUserGeometryList() { return UserGeometryList; }; + +private: + TArray<FUserDefinedGeometryDesc> UserGeometryList; +}; + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = OculusXRHMD) +class OCULUSXRHMD_API UOculusXRPassthroughLayerComponent : public UStereoLayerComponent +{ + GENERATED_UCLASS_BODY() + +public: + + void DestroyComponent(bool bPromoteChildren) override; + + void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction *ThisTickFunction) override; + + void UpdatePassthroughObjects(); + + UFUNCTION(BlueprintCallable, Category ="Passthrough") + void AddSurfaceGeometry(AStaticMeshActor* StaticMeshActor, bool updateTransform ); + + UFUNCTION(BlueprintCallable, Category ="Passthrough") + void RemoveSurfaceGeometry(AStaticMeshActor* StaticMeshActor ); + + UFUNCTION(BlueprintCallable, Category = "Passthrough") + bool IsSurfaceGeometry(AStaticMeshActor* StaticMeshActor ) const; + + // Manually mark the stereo layer passthrough effect for updating + UFUNCTION(BlueprintCallable, Category="Components|Stereo Layer") + void MarkPassthroughStyleForUpdate(); + +protected: + + virtual bool LayerRequiresTexture(); + +private: + + OculusXRHMD::FOculusPassthroughMeshRef CreatePassthroughMesh(UStaticMesh* StaticMesh); + + UPROPERTY(Transient) + TMap<FString, AStaticMeshActor*> PassthroughActorMap; + + /** Passthrough style needs to be marked for update **/ + bool bPassthroughStyleNeedsUpdate; + +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughLayerShapes.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughLayerShapes.h new file mode 100644 index 0000000000000000000000000000000000000000..88d0cd309750e564157c166460e891fba2659089 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughLayerShapes.h @@ -0,0 +1,156 @@ +#pragma once + +#include "StereoLayerShapes.h" +#include "OculusXRPassthroughMesh.h" + +UENUM() +enum EOculusXRColorMapType +{ + /** None*/ + ColorMapType_None UMETA(DisplayName = "None"), + + /** Grayscale to color */ + ColorMapType_GrayscaleToColor UMETA(DisplayName = "Grayscale To Color"), + + /** Grayscale */ + ColorMapType_Grayscale UMETA(DisplayName = "Grayscale"), + + /** Color Adjustment */ + ColorMapType_ColorAdjustment UMETA(DisplayName = "Color Adjustment"), + + ColorMapType_MAX, +}; + +UENUM() +enum EOculusXRPassthroughLayerOrder +{ + /** Layer is rendered on top of scene */ + PassthroughLayerOrder_Overlay UMETA(DisplayName = "Overlay"), + + /** Layer is rendered under scene */ + PassthroughLayerOrder_Underlay UMETA(DisplayName = "Underlay"), + + PassthroughLayerOrder_MAX, +}; + +struct FEdgeStyleParameters { + FEdgeStyleParameters() + : bEnableEdgeColor(false) + , bEnableColorMap(false) + , TextureOpacityFactor(1.0f) + , EdgeColor{} + , ColorMapType{} + , ColorMapData{} + { + + }; + + FEdgeStyleParameters( + bool bEnableEdgeColor, + bool bEnableColorMap, + float TextureOpacityFactor, + float Brightness, + float Contrast, + float Posterize, + float Saturation, + FLinearColor EdgeColor, + FLinearColor ColorScale, + FLinearColor ColorOffset, + EOculusXRColorMapType InColorMapType, + const TArray<FLinearColor>& InColorMapGradient) + : bEnableEdgeColor(bEnableEdgeColor) + , bEnableColorMap(bEnableColorMap) + , TextureOpacityFactor(TextureOpacityFactor) + , Brightness(Brightness) + , Contrast(Contrast) + , Posterize(Posterize) + , Saturation(Saturation) + , EdgeColor(EdgeColor) + , ColorScale(ColorScale) + , ColorOffset(ColorOffset) + , ColorMapType(InColorMapType) + + { + ColorMapData = GenerateColorMapData(InColorMapType, InColorMapGradient); + }; + + bool bEnableEdgeColor; + bool bEnableColorMap; + float TextureOpacityFactor; + float Brightness; + float Contrast; + float Posterize; + float Saturation; + FLinearColor EdgeColor; + FLinearColor ColorScale; + FLinearColor ColorOffset; + EOculusXRColorMapType ColorMapType; + TArray<uint8> ColorMapData; + +private: + + /** Generates the corresponding color map based on given color map type */ + TArray<uint8> GenerateColorMapData(EOculusXRColorMapType InColorMapType, const TArray<FLinearColor>& InColorMapGradient); + + /** Generates a grayscale to color color map based on given gradient --> It also applies the color scale and offset */ + TArray<uint8> GenerateMonoToRGBA(const TArray<FLinearColor>& InGradient, const TArray<uint8>& InColorMapData); + + /** Generates a grayscale color map with given Brightness/Contrast/Posterize settings */ + TArray<uint8> GenerateMonoBrightnessContrastPosterizeMap(); + + /** Generates a luminance based colormap from the the Brightness/Contrast */ + TArray<uint8> GenerateBrightnessContrastSaturationColorMap(); +}; + +class OCULUSXRHMD_API FReconstructedLayer : public IStereoLayerShape +{ + STEREO_LAYER_SHAPE_BOILERPLATE(FReconstructedLayer) + +public: + FReconstructedLayer() {}; + FReconstructedLayer(const FEdgeStyleParameters& EdgeStyleParameters, EOculusXRPassthroughLayerOrder PassthroughLayerOrder) + : EdgeStyleParameters(EdgeStyleParameters), + PassthroughLayerOrder(PassthroughLayerOrder) + { + }; + FEdgeStyleParameters EdgeStyleParameters; + EOculusXRPassthroughLayerOrder PassthroughLayerOrder; +}; + +struct FUserDefinedGeometryDesc +{ + FUserDefinedGeometryDesc(const FString& MeshName, OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh, const FTransform& Transform, bool bUpdateTransform) + : MeshName(MeshName) + , PassthroughMesh(PassthroughMesh) + , Transform(Transform) + , bUpdateTransform(bUpdateTransform) + { + }; + + FString MeshName; + OculusXRHMD::FOculusPassthroughMeshRef PassthroughMesh; + FTransform Transform; + bool bUpdateTransform; +}; + +class OCULUSXRHMD_API FUserDefinedLayer : public IStereoLayerShape +{ + STEREO_LAYER_SHAPE_BOILERPLATE(FUserDefinedLayer) + +public: + FUserDefinedLayer() {}; + FUserDefinedLayer(TArray<FUserDefinedGeometryDesc> InUserGeometryList, const FEdgeStyleParameters& EdgeStyleParameters, EOculusXRPassthroughLayerOrder PassthroughLayerOrder) + : UserGeometryList{} + , EdgeStyleParameters(EdgeStyleParameters) + , PassthroughLayerOrder(PassthroughLayerOrder) + { + UserGeometryList = InUserGeometryList; + } + + TArray<FUserDefinedGeometryDesc> UserGeometryList; + FEdgeStyleParameters EdgeStyleParameters; + EOculusXRPassthroughLayerOrder PassthroughLayerOrder; + +private: + +}; diff --git a/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughMesh.h b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughMesh.h new file mode 100644 index 0000000000000000000000000000000000000000..be690550d94b1c16e1a948a617e9f32476e6eb33 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRHMD/Public/OculusXRPassthroughMesh.h @@ -0,0 +1,28 @@ +#pragma once + +#include "CoreMinimal.h" + +namespace OculusXRHMD +{ + +class FOculusPassthroughMesh : public FRefCountedObject +{ +public: + FOculusPassthroughMesh(const TArray<FVector>& InVertices, const TArray<int32>& InTriangles) + : Vertices(InVertices) + , Triangles(InTriangles) + { + } + + const TArray<FVector>& GetVertices() const { return Vertices; }; + const TArray<int32>& GetTriangles() const { return Triangles; }; + +private: + TArray<FVector> Vertices; + TArray<int32> Triangles; +}; + +typedef TRefCountPtr<FOculusPassthroughMesh> FOculusPassthroughMeshRef; + +} // namespace OculusXRHMD + diff --git a/Plugins/OculusXR/Source/OculusXRInput/OculusXRInput.Build.cs b/Plugins/OculusXR/Source/OculusXRInput/OculusXRInput.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..9f096c854c4ed9fdd281024d935da4a6b8604c1e --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/OculusXRInput.Build.cs @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class OculusXRInput : ModuleRules + { + public OculusXRInput(ReadOnlyTargetRules Target) : base(Target) + { + PrivateIncludePathModuleNames.AddRange( + new string[] + { + "InputDevice", // For IInputDevice.h + "HeadMountedDisplay", // For IMotionController.h + "ImageWrapper" + }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "ApplicationCore", + "Engine", + "InputCore", + "HeadMountedDisplay", + "OculusXRHMD", + "OculusXRMR", + "OVRPluginXR", + }); + + PrivateIncludePaths.AddRange( + new string[] { + // Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source + "OculusXRHMD/Private", + }); + + PublicIncludePaths.AddRange( + new string[] { + "Runtime/Renderer/Private", + "Runtime/Engine/Classes/Components", + }); + + if (Target.Platform == UnrealTargetPlatform.Win64) + { + RuntimeDependencies.Add("$(PluginDir)/Source/ThirdParty/OVRPlugin/OVRPlugin/Lib/" + Target.Platform.ToString() + "/OVRPlugin.dll"); + } + + if (Target.Platform == UnrealTargetPlatform.Win64) + { + RuntimeDependencies.Add("$(PluginDir)/Source/ThirdParty/OVRPlugin/OVRPlugin/Lib/" + Target.Platform.ToString() + "/OpenXR/OVRPlugin.dll"); + } + } + } +} diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRControllerTracking.cpp b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRControllerTracking.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6c500d8d1dee5c1b864c2352a42b131abb384407 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRControllerTracking.cpp @@ -0,0 +1,66 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRControllerTracking.h" +#include "OculusXRHMD.h" +#include "OculusXRInput.h" +#include "Misc/CoreDelegates.h" +#include "IOculusXRInputModule.h" +#include "Haptics/HapticFeedbackEffect_Base.h" + +namespace OculusXRInput +{ + void FOculusXRControllerTracking::PlayHapticEffect( + class UHapticFeedbackEffect_Base* HapticEffect, + EControllerHand Hand, + EOculusXRHandHapticsLocation Location, + bool bAppend, + float Scale, + bool bLoop) + { +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + OculusXRInputModule.Get()->PlayHapticEffect(HapticEffect, Hand, Location, bAppend, Scale, bLoop); +#endif + } + + int FOculusXRControllerTracking::PlayHapticEffect(EControllerHand Hand, TArray<uint8>& Amplitudes, int SampleRate, bool bPCM, bool bAppend) + { +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + return OculusXRInputModule.Get()->PlayHapticEffect(Hand, Amplitudes.Num(), Amplitudes.GetData(), SampleRate, bPCM, bAppend); +#endif + } + + void FOculusXRControllerTracking::StopHapticEffect(EControllerHand Hand, EOculusXRHandHapticsLocation Location) + { +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + SetHapticsByValue(0.f, 0.f, Hand, Location); +#endif + } + + void FOculusXRControllerTracking::SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location) + { +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + OculusXRInputModule.Get()->SetHapticsByValue(Frequency, Amplitude, Hand, Location); +#endif + } + + float FOculusXRControllerTracking::GetControllerSampleRateHz(EControllerHand Hand) + { +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + return OculusXRInputModule.Get()->GetControllerSampleRateHz(Hand); +#endif + return 0; + } + + int FOculusXRControllerTracking::GetMaxHapticDuration(EControllerHand Hand) + { +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + return OculusXRInputModule.Get()->GetMaxHapticDuration(Hand); +#endif + return 0; + } +} diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRControllerTracking.h b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRControllerTracking.h new file mode 100644 index 0000000000000000000000000000000000000000..be4e61438775e5164f368ee2491e9667db4427cb --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRControllerTracking.h @@ -0,0 +1,42 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRInputFunctionLibrary.h" + +#define LOCTEXT_NAMESPACE "OculusXRControllerTracking" + +DEFINE_LOG_CATEGORY_STATIC(LogOcXRControllerTracking, Log, All); + +//------------------------------------------------------------------------------------------------- +// FOculusXRControllerTracking +//------------------------------------------------------------------------------------------------- + +class UHapticFeedbackEffect_Base; + +namespace OculusXRInput +{ +class FOculusXRControllerTracking +{ +public: + static void PlayHapticEffect( + UHapticFeedbackEffect_Base* HapticEffect, + EControllerHand Hand, + EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand, + bool bAppend = false, + float Scale = 1.f, + bool bLoop = false); + + static int PlayHapticEffect(EControllerHand Hand, TArray<uint8>& Amplitudes, int SampleRate, bool bPCM = false, bool bAppend = false); + + static void StopHapticEffect(EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand); + + static void SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand); + + static float GetControllerSampleRateHz(EControllerHand Hand); + + static int GetMaxHapticDuration(EControllerHand Hand); +}; + +} // namespace OculusXRInput + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandComponent.cpp b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0bac9ebf05a11a8ee59d2fe0b1a0afaf82d6b697 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandComponent.cpp @@ -0,0 +1,220 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "OculusXRHandComponent.h" +#include "OculusXRInput.h" + +#include "Engine/SkeletalMesh.h" +#include "Components/InputComponent.h" +#include "Materials/MaterialInterface.h" + +#include "GameFramework/PlayerController.h" + +UOculusXRHandComponent::UOculusXRHandComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + PrimaryComponentTick.TickGroup = TG_PrePhysics; + + bHasAuthority = false; + bAutoActivate = true; + + bWantsInitializeComponent = true; + + for (uint8 BoneIndex = 0; BoneIndex < (uint8)EOculusXRBone::Bone_Max; BoneIndex++) + { + BoneNameMappings.Add((EOculusXRBone)BoneIndex, TEXT("")); + } +} + +void UOculusXRHandComponent::BeginPlay() +{ + Super::BeginPlay(); + + // Use custom mesh if a skeletal mesh is already set, else try to load the runtime mesh + if (SkeletalMesh) + { + bCustomHandMesh = true; + bSkeletalMeshInitialized = true; + } + else + { + RuntimeSkeletalMesh = NewObject<USkeletalMesh>(this, TEXT("OculusHandMesh")); + InitializeSkeletalMesh(); + } +} + +void UOculusXRHandComponent::InitializeSkeletalMesh() +{ + if (RuntimeSkeletalMesh) + { + if (UOculusXRInputFunctionLibrary::GetHandSkeletalMesh(RuntimeSkeletalMesh, SkeletonType, MeshType)) + { + SetSkeletalMesh(RuntimeSkeletalMesh); + if (MaterialOverride) + { + SetMaterial(0, MaterialOverride); + } + CachedBaseMaterial = GetMaterial(0); + bSkeletalMeshInitialized = true; + + // Initialize physics capsules on the runtime mesh + if (bInitializePhysics) + { + CollisionCapsules = UOculusXRInputFunctionLibrary::InitializeHandPhysics(SkeletonType, this); + } + } + } +} + +void UOculusXRHandComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + +#if WITH_EDITOR + if (!bSkeletalMeshInitialized && !bCustomHandMesh) + { + InitializeSkeletalMesh(); + } +#endif + + if (IsInGameThread()) + { + // Cache state from the game thread for use on the render thread + const AActor* MyOwner = GetOwner(); + bHasAuthority = MyOwner->HasLocalNetOwner(); + int i = 0; + } + + if (bHasAuthority) + { + bool bHidden = false; + if (UOculusXRInputFunctionLibrary::IsHandTrackingEnabled()) + { + // Update Visibility based on Confidence + if (ConfidenceBehavior == EOculusXRConfidenceBehavior::HideActor) + { + EOculusXRTrackingConfidence TrackingConfidence = UOculusXRInputFunctionLibrary::GetTrackingConfidence(SkeletonType); + bHidden |= TrackingConfidence != EOculusXRTrackingConfidence::High; + } + + // Update Hand Scale + if (bUpdateHandScale) + { + float NewScale = UOculusXRInputFunctionLibrary::GetHandScale(SkeletonType); + SetRelativeScale3D(FVector(NewScale)); + } + + // Update Bone Pose Rotations + if (SkeletalMesh) + { + UpdateBonePose(); + } + +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + // Check for system gesture pressed through player controller + if (APawn* Pawn = Cast<APawn>(GetOwner())) + { + if (APlayerController* PC = Pawn->GetController<APlayerController>()) + { + if (PC->WasInputKeyJustPressed(SkeletonType == EOculusXRHandType::HandLeft ? OculusXRInput::FOculusKey::OculusHand_Left_SystemGesture : OculusXRInput::FOculusKey::OculusHand_Right_SystemGesture)) + { + SystemGesturePressed(); + } + if (PC->WasInputKeyJustReleased(SkeletonType == EOculusXRHandType::HandLeft ? OculusXRInput::FOculusKey::OculusHand_Left_SystemGesture : OculusXRInput::FOculusKey::OculusHand_Right_SystemGesture)) + { + SystemGestureReleased(); + } + } + } +#endif + } + else + { + bHidden = true; + } + + if (bHidden != bHiddenInGame) + { + SetHiddenInGame(bHidden); + for (int32 i = 0; i < CollisionCapsules.Num(); i++) + { + CollisionCapsules[i].Capsule->SetCollisionEnabled(bHidden ? ECollisionEnabled::NoCollision : ECollisionEnabled::QueryAndPhysics); + } + } + } +} + +void UOculusXRHandComponent::UpdateBonePose() +{ + if (bCustomHandMesh) + { + for (auto& BoneElem : BoneNameMappings) + { + // Set Root Bone Rotaiton + if (BoneElem.Key == EOculusXRBone::Wrist_Root) + { + FQuat RootBoneRotation = UOculusXRInputFunctionLibrary::GetBoneRotation(SkeletonType, EOculusXRBone::Wrist_Root); + RootBoneRotation *= HandRootFixupRotation; + RootBoneRotation.Normalize(); + BoneSpaceTransforms[0].SetRotation(RootBoneRotation); + + } + else + { + // Set Remaing Bone Rotations + int32 BoneIndex = SkeletalMesh->GetRefSkeleton().FindBoneIndex(BoneElem.Value); + if (BoneIndex >= 0) + { + FQuat BoneRotation = UOculusXRInputFunctionLibrary::GetBoneRotation(SkeletonType, (EOculusXRBone)BoneElem.Key); + BoneSpaceTransforms[BoneIndex].SetRotation(BoneRotation); + } + } + } + } + else + { + // Set Root Bone Rotation + FQuat RootBoneRotation = UOculusXRInputFunctionLibrary::GetBoneRotation(SkeletonType, EOculusXRBone::Wrist_Root); + RootBoneRotation *= HandRootFixupRotation; + RootBoneRotation.Normalize(); + BoneSpaceTransforms[0].SetRotation(RootBoneRotation); + + // Set Remaining Bone Rotations + for (uint32 BoneIndex = 1; BoneIndex < (uint32)SkeletalMesh->GetRefSkeleton().GetNum(); BoneIndex++) + { + FQuat BoneRotation = UOculusXRInputFunctionLibrary::GetBoneRotation(SkeletonType, (EOculusXRBone)BoneIndex); + BoneSpaceTransforms[BoneIndex].SetRotation(BoneRotation); + } + } + MarkRefreshTransformDirty(); +} + +void UOculusXRHandComponent::SystemGesturePressed() +{ + if (SystemGestureBehavior == EOculusXRSystemGestureBehavior::SwapMaterial) + { + if (SystemGestureMaterial) + { + SetMaterial(0, SystemGestureMaterial); + } + else + { + UE_LOG(LogTemp, Log, TEXT("System Gesture Behavior was set to Swap Material but no System Gesture Material was provided!")); + } + } +} + +void UOculusXRHandComponent::SystemGestureReleased() +{ + if (SystemGestureBehavior == EOculusXRSystemGestureBehavior::SwapMaterial) + { + if (CachedBaseMaterial) + { + SetMaterial(0, CachedBaseMaterial); + } + else + { + UE_LOG(LogTemp, Log, TEXT("System Gesture Behavior was set to Swap Material but no System Gesture Material was provided!")); + } + } +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandTracking.cpp b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandTracking.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bfce0d273073de99ddc46e1d60530d83d994f714 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandTracking.cpp @@ -0,0 +1,629 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRHandTracking.h" +#include "OculusXRHMD.h" +#include "Misc/CoreDelegates.h" +#include "IOculusXRInputModule.h" + +#include "Components/SkeletalMeshComponent.h" +#include "Rendering/SkeletalMeshLODRenderData.h" +#include "Rendering/SkeletalMeshRenderData.h" +#include "Rendering/SkeletalMeshLODModel.h" +#include "Rendering/SkeletalMeshModel.h" +#include "Materials/Material.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "Model.h" + +#define OCULUS_TO_UE4_SCALE 100.0f + +namespace OculusXRInput +{ + +FQuat FOculusHandTracking::GetBoneRotation(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const EOculusXRBone BoneId) +{ + FQuat Rotation = FQuat::Identity; + if (BoneId <= EOculusXRBone::Invalid && BoneId >= EOculusXRBone::Bone_Max) + { + return Rotation; + } + +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + if (OculusXRInputModule.IsValid()) + { + TArray<FOculusControllerPair> ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; + for (const FOculusControllerPair& HandPair : ControllerPairs) + { + if (HandPair.UnrealControllerIndex == ControllerIndex) + { + if (DeviceHand != EOculusXRHandType::None) + { + ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; + const FOculusHandControllerState& HandState = HandPair.HandControllerStates[Hand]; + int32 OvrBoneId = ToOvrBone(BoneId); + Rotation = HandState.BoneRotations[OvrBoneId]; + break; + } + } + } + } +#endif + + return Rotation; +} + +float FOculusHandTracking::GetHandScale(const int32 ControllerIndex, const EOculusXRHandType DeviceHand) +{ +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + if (OculusXRInputModule.IsValid()) + { + TArray<FOculusControllerPair> ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; + for (const FOculusControllerPair& HandPair : ControllerPairs) + { + if (HandPair.UnrealControllerIndex == ControllerIndex) + { + if (DeviceHand != EOculusXRHandType::None) + { + ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; + return HandPair.HandControllerStates[Hand].HandScale; + } + } + } + } +#endif + return 1.0f; +} + +EOculusXRTrackingConfidence FOculusHandTracking::GetTrackingConfidence(const int32 ControllerIndex, const EOculusXRHandType DeviceHand) +{ +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + if (OculusXRInputModule.IsValid()) + { + TArray<FOculusControllerPair> ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; + for (const FOculusControllerPair& HandPair : ControllerPairs) + { + if (HandPair.UnrealControllerIndex == ControllerIndex) + { + if (DeviceHand != EOculusXRHandType::None) + { + ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; + return HandPair.HandControllerStates[Hand].TrackingConfidence; + } + } + } + } +#endif + return EOculusXRTrackingConfidence::Low; +} + +EOculusXRTrackingConfidence FOculusHandTracking::GetFingerTrackingConfidence(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const EOculusHandAxes Finger) +{ + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + if (OculusXRInputModule.IsValid()) + { + TArray<FOculusControllerPair> ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; + for (const FOculusControllerPair& HandPair : ControllerPairs) + { + if (HandPair.UnrealControllerIndex == ControllerIndex) + { + if (DeviceHand != EOculusXRHandType::None) + { + ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; + return HandPair.HandControllerStates[Hand].FingerConfidences[(int)Finger]; + } + } + } + } + return EOculusXRTrackingConfidence::Low; +} + +FTransform FOculusHandTracking::GetPointerPose(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const float WorldToMeters) +{ +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + if (OculusXRInputModule.IsValid()) + { + TArray<FOculusControllerPair> ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; + for (const FOculusControllerPair& HandPair : ControllerPairs) + { + if (HandPair.UnrealControllerIndex == ControllerIndex) + { + if (DeviceHand != EOculusXRHandType::None) + { + ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; + FTransform PoseTransform = HandPair.HandControllerStates[Hand].PointerPose; + PoseTransform.SetLocation(PoseTransform.GetLocation() * WorldToMeters); + return PoseTransform; + } + } + } + } +#endif + return FTransform(); +} + +bool FOculusHandTracking::IsPointerPoseValid(const int32 ControllerIndex, const EOculusXRHandType DeviceHand) +{ +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + if (OculusXRInputModule.IsValid()) + { + TArray<FOculusControllerPair> ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; + for (const FOculusControllerPair& HandPair : ControllerPairs) + { + if (HandPair.UnrealControllerIndex == ControllerIndex) + { + if (DeviceHand != EOculusXRHandType::None) + { + ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; + return HandPair.HandControllerStates[Hand].bIsPointerPoseValid; + } + } + } + } +#endif + return false; +} + +bool FOculusHandTracking::IsHandTrackingEnabled() +{ +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + ovrpBool result; + FOculusXRHMDModule::GetPluginWrapper().GetHandTrackingEnabled(&result); + return result == ovrpBool_True; +#else + return false; +#endif +} + +bool FOculusHandTracking::IsHandDominant(const int32 ControllerIndex, const EOculusXRHandType DeviceHand) +{ +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + if (OculusXRInputModule.IsValid()) + { + TArray<FOculusControllerPair> ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; + for (const FOculusControllerPair& HandPair : ControllerPairs) + { + if (HandPair.UnrealControllerIndex == ControllerIndex) + { + if (DeviceHand != EOculusXRHandType::None) + { + ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; + return HandPair.HandControllerStates[Hand].bIsDominantHand; + } + } + } + } +#endif + return false; +} + +bool FOculusHandTracking::IsHandPositionValid(int32 ControllerIndex, EOculusXRHandType DeviceHand) +{ + TSharedPtr<FOculusXRInput> OculusXRInputModule = StaticCastSharedPtr<FOculusXRInput>(IOculusXRInputModule::Get().GetInputDevice()); + if (OculusXRInputModule.IsValid()) + { + TArray<FOculusControllerPair> ControllerPairs = OculusXRInputModule.Get()->ControllerPairs; + for (const FOculusControllerPair& HandPair : ControllerPairs) + { + if (HandPair.UnrealControllerIndex == ControllerIndex) + { + if (DeviceHand != EOculusXRHandType::None) + { + ovrpHand Hand = DeviceHand == EOculusXRHandType::HandLeft ? ovrpHand_Left : ovrpHand_Right; + return HandPair.HandControllerStates[Hand].bIsPositionValid; + } + } + } + } + return false; +} + +bool FOculusHandTracking::GetHandSkeletalMesh(USkeletalMesh* HandSkeletalMesh, const EOculusXRHandType SkeletonType, const EOculusXRHandType MeshType, const float WorldToMeters) +{ +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + if (HandSkeletalMesh) + { + ovrpMesh* OvrMesh = new ovrpMesh(); + ovrpSkeleton2* OvrSkeleton = new ovrpSkeleton2(); + + ovrpSkeletonType OvrSkeletonType = (ovrpSkeletonType)((int32)SkeletonType - 1); + ovrpMeshType OvrMeshType = (ovrpMeshType)((int32)MeshType - 1); + ovrpResult SkelResult = FOculusXRHMDModule::GetPluginWrapper().GetSkeleton2(OvrSkeletonType, OvrSkeleton); + ovrpResult MeshResult = FOculusXRHMDModule::GetPluginWrapper().GetMesh(OvrMeshType, OvrMesh); + if (SkelResult != ovrpSuccess || MeshResult != ovrpSuccess) + { +#if !WITH_EDITOR + UE_LOG(LogOcHandTracking, Error, TEXT("Failed to get mesh or skeleton data from Oculus runtime.")); +#endif + delete OvrMesh; + delete OvrSkeleton; + + return false; + } + + // Create Skeletal Mesh LOD Render Data +#if WITH_EDITOR + FSkeletalMeshLODModel* LodRenderData = new FSkeletalMeshLODModel(); + HandSkeletalMesh->GetImportedModel()->LODModels.Add(LodRenderData); +#else + FSkeletalMeshLODRenderData* LodRenderData = new FSkeletalMeshLODRenderData(); + HandSkeletalMesh->AllocateResourceForRendering(); + HandSkeletalMesh->GetResourceForRendering()->LODRenderData.Add(LodRenderData); +#endif + + // Set default LOD Info + FSkeletalMeshLODInfo& LodInfo = HandSkeletalMesh->AddLODInfo(); + LodInfo.ScreenSize = 0.3f; + LodInfo.LODHysteresis = 0.2f; + LodInfo.BuildSettings.bUseFullPrecisionUVs = true; + + InitializeHandSkeleton(HandSkeletalMesh, OvrSkeleton, WorldToMeters); + + // Add default material as backup + LodInfo.LODMaterialMap.Add(0); + UMaterialInterface* DefaultMaterial = UMaterial::GetDefaultMaterial(MD_Surface); + HandSkeletalMesh->GetMaterials().Add(DefaultMaterial); + HandSkeletalMesh->GetMaterials()[0].UVChannelData.bInitialized = true; + + // Set skeletal mesh properties + HandSkeletalMesh->SetHasVertexColors(true); + HandSkeletalMesh->SetHasBeenSimplified(false); + HandSkeletalMesh->SetEnablePerPolyCollision(false); + + InitializeHandMesh(HandSkeletalMesh, OvrMesh, WorldToMeters); + +#if WITH_EDITOR + HandSkeletalMesh->InvalidateDeriveDataCacheGUID(); + HandSkeletalMesh->PostEditChange(); +#endif + + // Create Skeleton object and merge all bones + HandSkeletalMesh->SetSkeleton(NewObject<USkeleton>()); + HandSkeletalMesh->GetSkeleton()->MergeAllBonesToBoneTree(HandSkeletalMesh); + HandSkeletalMesh->PostLoad(); + + delete OvrMesh; + delete OvrSkeleton; + + return true; + } +#endif + return false; +} + +void FOculusHandTracking::InitializeHandMesh(USkeletalMesh* SkeletalMesh, const ovrpMesh* OvrMesh, const float WorldToMeters) +{ +#if WITH_EDITOR + FSkeletalMeshLODModel* LodRenderData = &SkeletalMesh->GetImportedModel()->LODModels[0]; + + // Initialize mesh section + LodRenderData->Sections.SetNumUninitialized(1); + new(&LodRenderData->Sections[0]) FSkelMeshSection(); + auto& MeshSection = LodRenderData->Sections[0]; + + // Set default mesh section properties + MeshSection.MaterialIndex = 0; + MeshSection.BaseIndex = 0; + MeshSection.NumTriangles = OvrMesh->NumIndices / 3; + MeshSection.BaseVertexIndex = 0; + MeshSection.MaxBoneInfluences = 4; + MeshSection.NumVertices = OvrMesh->NumVertices; + + float MaxDistSq = MIN_flt; + for (uint32_t VertexIndex = 0; VertexIndex < OvrMesh->NumVertices; VertexIndex++) + { + FSoftSkinVertex SoftVertex; + + // Update vertex data + SoftVertex.Color = FColor::White; + ovrpVector3f VertexPosition = OvrMesh->VertexPositions[VertexIndex]; + ovrpVector3f Normal = OvrMesh->VertexNormals[VertexIndex]; + SoftVertex.Position = FVector3f(VertexPosition.x, VertexPosition.z, VertexPosition.y) * WorldToMeters; + SoftVertex.TangentZ = FVector3f(Normal.x, Normal.z, Normal.y); + SoftVertex.TangentX = FVector3f(1.0f, 0.0f, 0.0f); + SoftVertex.TangentY = FVector3f(0.0f, 1.0f, 0.0f);// SoftVertex.TangentZ^ SoftVertex.TangentX* SoftVertex.TangentZ.W; + SoftVertex.UVs[0] = FVector2f(OvrMesh->VertexUV0[VertexIndex].x, OvrMesh->VertexUV0[VertexIndex].y); + + // Update the Bounds + float VertexDistSq = SoftVertex.Position.SizeSquared(); + if (VertexDistSq > MaxDistSq) + MaxDistSq = VertexDistSq; + + // Update blend weights and indices + ovrpVector4f BlendWeights = OvrMesh->BlendWeights[VertexIndex]; + ovrpVector4s BlendIndices = OvrMesh->BlendIndices[VertexIndex]; + + uint8 TotalWeight = 0; + SoftVertex.InfluenceWeights[0] = 255.f * BlendWeights.x; + SoftVertex.InfluenceBones[0] = BlendIndices.x; + TotalWeight += 255.f * BlendWeights.x; + SoftVertex.InfluenceWeights[1] = 255.f * BlendWeights.y; + SoftVertex.InfluenceBones[1] = BlendIndices.y; + TotalWeight += 255.f * BlendWeights.y; + SoftVertex.InfluenceWeights[2] = 255.f * BlendWeights.z; + SoftVertex.InfluenceBones[2] = BlendIndices.z; + TotalWeight += 255.f * BlendWeights.z; + SoftVertex.InfluenceWeights[3] = 255.f * BlendWeights.w; + SoftVertex.InfluenceBones[3] = BlendIndices.w; + TotalWeight += 255.f * BlendWeights.w; + + MeshSection.SoftVertices.Add(SoftVertex); + } + + // Update bone map + for (uint32 BoneIndex = 0; BoneIndex < (uint32)SkeletalMesh->GetRefSkeleton().GetNum(); BoneIndex++) + { + MeshSection.BoneMap.Add(BoneIndex); + } + + // Update LOD render data + LodRenderData->NumVertices = OvrMesh->NumVertices; + LodRenderData->NumTexCoords = 1; + + // Create index buffer + for (uint32_t Index = 0; Index < OvrMesh->NumIndices; Index++) + { + LodRenderData->IndexBuffer.Add(OvrMesh->Indices[Index]); + } + + // Finalize Bounds + float MaxDist = FMath::Sqrt(MaxDistSq); + FBoxSphereBounds Bounds; + Bounds.Origin = FVector::ZeroVector; + Bounds.BoxExtent = FVector(MaxDist); + Bounds.SphereRadius = MaxDist; + SkeletalMesh->SetImportedBounds(Bounds); + +#else + FSkeletalMeshLODRenderData* LodRenderData = &SkeletalMesh->GetResourceForRendering()->LODRenderData[0]; + + // Initialize Mesh Section + LodRenderData->RenderSections.SetNumUninitialized(1); + new(&LodRenderData->RenderSections[0]) FSkelMeshRenderSection(); + auto& MeshSection = LodRenderData->RenderSections[0]; + + // Initialize render section properties + MeshSection.MaterialIndex = 0; + MeshSection.BaseIndex = 0; + MeshSection.NumTriangles = OvrMesh->NumIndices / 3; + MeshSection.BaseVertexIndex = 0; + MeshSection.MaxBoneInfluences = 4; + MeshSection.NumVertices = OvrMesh->NumVertices; + MeshSection.bCastShadow = true; + MeshSection.bDisabled = false; + MeshSection.bRecomputeTangent = false; + + // Initialize Vertex Buffers + LodRenderData->StaticVertexBuffers.PositionVertexBuffer.Init(OvrMesh->NumVertices); + LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.Init(OvrMesh->NumVertices, 1); + LodRenderData->StaticVertexBuffers.ColorVertexBuffer.Init(OvrMesh->NumVertices); + + // Initialize Skin Weights + TArray<FSkinWeightInfo> InWeights; + InWeights.AddUninitialized(OvrMesh->NumVertices); + + float MaxDistSq = MIN_flt; + TMap<int32, TArray<int32>> OverlappingVertices; + for (uint32_t VertexIndex = 0; VertexIndex < OvrMesh->NumVertices; VertexIndex++) + { + // Initialize vertex data + FModelVertex ModelVertex; + + // Update Model Vertex + ovrpVector3f VertexPosition = OvrMesh->VertexPositions[VertexIndex]; + ovrpVector3f Normal = OvrMesh->VertexNormals[VertexIndex]; + ModelVertex.Position = FVector3f(VertexPosition.x, VertexPosition.z, VertexPosition.y) * WorldToMeters; + ModelVertex.TangentZ = FVector3f(Normal.x, Normal.z, Normal.y); + ModelVertex.TangentX = FVector3f(1.0f, 0.0f, 0.0f); + ModelVertex.TexCoord = FVector2f(OvrMesh->VertexUV0[VertexIndex].x, OvrMesh->VertexUV0[VertexIndex].y); + + // Add Model Vertex data to vertex buffer + LodRenderData->StaticVertexBuffers.PositionVertexBuffer.VertexPosition(VertexIndex) = ModelVertex.Position; + LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexTangents(VertexIndex, ModelVertex.TangentX, ModelVertex.GetTangentY(), ModelVertex.TangentZ); + LodRenderData->StaticVertexBuffers.StaticMeshVertexBuffer.SetVertexUV(VertexIndex, 0, ModelVertex.TexCoord); + + // Update the Bounds + float VertexDistSq = ModelVertex.Position.SizeSquared(); + if (VertexDistSq > MaxDistSq) + MaxDistSq = VertexDistSq; + + // Set vertex blend weights and indices + TArray<int32> Vertices; + ovrpVector4f BlendWeights = OvrMesh->BlendWeights[VertexIndex]; + ovrpVector4s BlendIndices = OvrMesh->BlendIndices[VertexIndex]; + + InWeights[VertexIndex].InfluenceWeights[0] = 255.f * BlendWeights.x; + InWeights[VertexIndex].InfluenceBones[0] = BlendIndices.x; + Vertices.Add(BlendIndices.x); + InWeights[VertexIndex].InfluenceWeights[1] = 255.f * BlendWeights.y; + InWeights[VertexIndex].InfluenceBones[1] = BlendIndices.y; + Vertices.Add(BlendIndices.y); + InWeights[VertexIndex].InfluenceWeights[2] = 255.f * BlendWeights.z; + InWeights[VertexIndex].InfluenceBones[2] = BlendIndices.z; + Vertices.Add(BlendIndices.z); + InWeights[VertexIndex].InfluenceWeights[3] = 255.f * BlendWeights.w; + InWeights[VertexIndex].InfluenceBones[3] = BlendIndices.w; + Vertices.Add(BlendIndices.w); + + OverlappingVertices.Add(VertexIndex, Vertices); + } + + // Update bone map for mesh section + for (uint32 BoneIndex = 0; BoneIndex < (uint32)SkeletalMesh->GetRefSkeleton().GetNum(); BoneIndex++) + { + MeshSection.BoneMap.Add(BoneIndex); + } + + // Finalize Bounds + float MaxDist = FMath::Sqrt(MaxDistSq); + FBoxSphereBounds Bounds; + Bounds.Origin = FVector::ZeroVector; + Bounds.BoxExtent = FVector(MaxDist); + Bounds.SphereRadius = MaxDist; + SkeletalMesh->SetImportedBounds(Bounds); + + // Assign skin weights to vertex buffer + LodRenderData->SkinWeightVertexBuffer = InWeights; + MeshSection.DuplicatedVerticesBuffer.Init(OvrMesh->NumVertices, OverlappingVertices); + + // Set index buffer + LodRenderData->MultiSizeIndexContainer.CreateIndexBuffer(sizeof(uint16_t)); + for (uint32_t Index = 0; Index < OvrMesh->NumIndices; Index++) + { + LodRenderData->MultiSizeIndexContainer.GetIndexBuffer()->AddItem(OvrMesh->Indices[Index]); + } +#endif +} + +void FOculusHandTracking::InitializeHandSkeleton(USkeletalMesh* SkeletalMesh, const ovrpSkeleton2* OvrSkeleton, const float WorldToMeters) +{ + SkeletalMesh->GetRefSkeleton().Empty(OvrSkeleton->NumBones); + +#if WITH_EDITOR + FSkeletalMeshLODModel* LodRenderData = &SkeletalMesh->GetImportedModel()->LODModels[0]; +#else + FSkeletalMeshLODRenderData* LodRenderData = &SkeletalMesh->GetResourceForRendering()->LODRenderData[0]; +#endif + SkeletalMesh->SetHasBeenSimplified(false); + SkeletalMesh->SetHasVertexColors(true); + + for (uint32 BoneIndex = 0; BoneIndex < OvrSkeleton->NumBones; BoneIndex++) + { + LodRenderData->ActiveBoneIndices.Add(BoneIndex); + LodRenderData->RequiredBones.Add(BoneIndex); + + FString BoneString = GetBoneName(BoneIndex); + FName BoneName = FName(*BoneString); + + FTransform Transform = FTransform::Identity; + FVector BonePosition = OvrBoneVectorToFVector(OvrSkeleton->Bones[BoneIndex].Pose.Position, WorldToMeters); + FQuat BoneRotation = BoneIndex == 0 ? FQuat(-1.0f, 0.0f, 0.0f, 1.0f) : OvrBoneQuatToFQuat(OvrSkeleton->Bones[BoneIndex].Pose.Orientation); + Transform.SetLocation(BonePosition); + Transform.SetRotation(BoneRotation); + + FReferenceSkeletonModifier Modifier = FReferenceSkeletonModifier(SkeletalMesh->GetRefSkeleton(), nullptr); + int32 ParentIndex = -1; + if (BoneIndex > 0) + { + if (OvrSkeleton->Bones[BoneIndex].ParentBoneIndex == ovrpBoneId::ovrpBoneId_Invalid) + { + ParentIndex = 0; + } + else + { + ParentIndex = OvrSkeleton->Bones[BoneIndex].ParentBoneIndex; + } + } + Modifier.Add(FMeshBoneInfo(BoneName, BoneString, ParentIndex), Transform); + } + SkeletalMesh->CalculateInvRefMatrices(); +} + +TArray<FOculusXRCapsuleCollider> FOculusHandTracking::InitializeHandPhysics(const EOculusXRHandType SkeletonType, USkinnedMeshComponent* HandComponent, const float WorldToMeters) +{ + TArray<FOculusXRCapsuleCollider> CollisionCapsules; + ovrpSkeleton2* OvrSkeleton = new ovrpSkeleton2(); + +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + ovrpSkeletonType OvrSkeletonType = (ovrpSkeletonType)((int32)SkeletonType - 1); + if (FOculusXRHMDModule::GetPluginWrapper().GetSkeleton2(OvrSkeletonType, OvrSkeleton) != ovrpSuccess) + { +#if !WITH_EDITOR + UE_LOG(LogOcHandTracking, Error, TEXT("Failed to get skeleton data from Oculus runtime.")); +#endif + delete OvrSkeleton; + return CollisionCapsules; + } +#endif + + TArray<UPrimitiveComponent*> IgnoreCapsules; + CollisionCapsules.AddDefaulted(OvrSkeleton->NumBoneCapsules); + for (uint32 CapsuleIndex = 0; CapsuleIndex < OvrSkeleton->NumBoneCapsules; CapsuleIndex++) + { + ovrpBoneCapsule OvrBoneCapsule = OvrSkeleton->BoneCapsules[CapsuleIndex]; + + UCapsuleComponent* Capsule = NewObject<UCapsuleComponent>(HandComponent); + + FVector CapsulePointZero = OvrBoneVectorToFVector(OvrBoneCapsule.Points[0], WorldToMeters); + FVector CapsulePointOne = OvrBoneVectorToFVector(OvrBoneCapsule.Points[1], WorldToMeters); + FVector Delta = (CapsulePointOne - CapsulePointZero); + + FName BoneName = HandComponent->SkeletalMesh->GetRefSkeleton().GetBoneName(OvrBoneCapsule.BoneIndex); + + float CapsuleHeight = Delta.Size(); + float CapsuleRadius = OvrBoneCapsule.Radius * WorldToMeters; + + Capsule->SetCapsuleRadius(CapsuleRadius); + Capsule->SetCapsuleHalfHeight(Delta.Size() / 2 + CapsuleRadius); + Capsule->SetupAttachment(HandComponent, BoneName); + Capsule->SetCollisionProfileName(HandComponent->GetCollisionProfileName()); + Capsule->RegisterComponentWithWorld(HandComponent->GetWorld()); + Capsule->SetCollisionEnabled(ECollisionEnabled::QueryAndPhysics); + FRotator CapsuleRotation = FQuat::FindBetweenVectors(FVector::RightVector, Delta).Rotator() + FRotator(0, 0, 90);; + + Capsule->SetRelativeRotation(CapsuleRotation); + Capsule->SetRelativeLocation(CapsulePointZero + (Delta / 2)); + + CollisionCapsules[CapsuleIndex].Capsule = Capsule; + CollisionCapsules[CapsuleIndex].BoneId = (EOculusXRBone)OvrBoneCapsule.BoneIndex; + + IgnoreCapsules.Add(Capsule); + } + + for (int32 CapsuleIndex = 0; CapsuleIndex < CollisionCapsules.Num(); CapsuleIndex++) + { + CollisionCapsules[CapsuleIndex].Capsule->MoveIgnoreComponents = IgnoreCapsules; + } + + return CollisionCapsules; +} + +ovrpBoneId FOculusHandTracking::ToOvrBone(EOculusXRBone Bone) +{ + if (Bone > EOculusXRBone::Bone_Max) + return ovrpBoneId_Invalid; + + return (ovrpBoneId)Bone; +} + +FString FOculusHandTracking::GetBoneName(uint8 Bone) +{ + uint8 HandBone = Bone == ovrpBoneId_Invalid ? (uint8)EOculusXRBone::Invalid : Bone; + + UEnum* BoneEnum = FindObject<UEnum>(ANY_PACKAGE, TEXT("EOculusXRBone"), true); + if (BoneEnum) + { + return BoneEnum->GetDisplayNameTextByValue(HandBone).ToString(); + } + return FString("Invalid"); +} + +EOculusXRTrackingConfidence FOculusHandTracking::ToEOculusXRTrackingConfidence(ovrpTrackingConfidence Confidence) +{ + EOculusXRTrackingConfidence TrackingConfidence = EOculusXRTrackingConfidence::Low; + switch (Confidence) + { + case ovrpTrackingConfidence_Low: + TrackingConfidence = EOculusXRTrackingConfidence::Low; + break; + case ovrpTrackingConfidence_High: + TrackingConfidence = EOculusXRTrackingConfidence::High; + break; + } + return TrackingConfidence; +} + +FVector FOculusHandTracking::OvrBoneVectorToFVector(ovrpVector3f ovrpVector, float WorldToMeters) +{ + return FVector(ovrpVector.x, -ovrpVector.y, ovrpVector.z) * WorldToMeters; +} + +FQuat FOculusHandTracking::OvrBoneQuatToFQuat(ovrpQuatf ovrpQuat) +{ + return FQuat(ovrpQuat.x, -ovrpQuat.y, ovrpQuat.z, -ovrpQuat.w); +} +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandTracking.h b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandTracking.h new file mode 100644 index 0000000000000000000000000000000000000000..6a02f628b72fb2825eb0465b097ccda50327e0f5 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRHandTracking.h @@ -0,0 +1,54 @@ +// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRHMDModule.h" +#include "OculusXRInput.h" +#include "Engine/SkeletalMesh.h" +#include "Components/CapsuleComponent.h" + +#include "OculusXRInputFunctionLibrary.h" + +#define LOCTEXT_NAMESPACE "OculusHandTracking" + +DEFINE_LOG_CATEGORY_STATIC(LogOcHandTracking, Log, All); + +//------------------------------------------------------------------------------------------------- +// FOculusHandTracking +//------------------------------------------------------------------------------------------------- +namespace OculusXRInput +{ +class FOculusHandTracking +{ +public: + // Oculus Hand Tracking + static FQuat GetBoneRotation(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const EOculusXRBone BoneId); + static float GetHandScale(const int32 ControllerIndex, const EOculusXRHandType DeviceHand); + static EOculusXRTrackingConfidence GetTrackingConfidence(const int32 ControllerIndex, const EOculusXRHandType DeviceHand); + static EOculusXRTrackingConfidence GetFingerTrackingConfidence(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const EOculusHandAxes Finger); // OCULUS STRIKE + static FTransform GetPointerPose(const int32 ControllerIndex, const EOculusXRHandType DeviceHand, const float WorldToMeters = 100.f); + static bool IsPointerPoseValid(const int32 ControllerIndex, const EOculusXRHandType DeviceHand); + static bool GetHandSkeletalMesh(USkeletalMesh* HandSkeletalMesh, const EOculusXRHandType SkeletonType, const EOculusXRHandType MeshType, const float WorldToMeters = 100.f); + static TArray<FOculusXRCapsuleCollider> InitializeHandPhysics(const EOculusXRHandType SkeletonType, USkinnedMeshComponent* HandComponent, const float WorldToMeters = 100.f); + static EOculusXRTrackingConfidence ToEOculusXRTrackingConfidence(ovrpTrackingConfidence Confidence); + static bool IsHandTrackingEnabled(); + static bool IsHandDominant(const int32 ControllerIndex, const EOculusXRHandType DeviceHand); + static bool IsHandPositionValid(int32 ControllerIndex, EOculusXRHandType DeviceHand); + + // Helper functions + static ovrpBoneId ToOvrBone(EOculusXRBone Bone); + static FString GetBoneName(uint8 Bone); + + // Converters for converting from ovr bone space (should match up with ovr avatar) + static FVector OvrBoneVectorToFVector(ovrpVector3f ovrpVector, float WorldToMeters); + static FQuat OvrBoneQuatToFQuat(ovrpQuatf ovrpQuat); + +private: + // Initializers for runtime hand assets + static void InitializeHandMesh(USkeletalMesh* SkeletalMesh, const ovrpMesh* OvrMesh, const float WorldToMeters); + static void InitializeHandSkeleton(USkeletalMesh* SkeletalMesh, const ovrpSkeleton2* OvrSkeleton, const float WorldToMeters); +}; + + +} // namespace OculusXRInput + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInput.cpp b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInput.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a71cf2c16fd043384413b7141201a51fa3e82579 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInput.cpp @@ -0,0 +1,1753 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRInput.h" + +#if OCULUS_INPUT_SUPPORTED_PLATFORMS +#include "OculusXRHMD.h" +#include "OculusXRHandTracking.h" +#include "OculusXRMRFunctionLibrary.h" +#include "Misc/CoreDelegates.h" +#include "Features/IModularFeatures.h" +#include "Misc/ConfigCacheIni.h" +#include "Haptics/HapticFeedbackEffect_Base.h" + +#define OVR_DEBUG_LOGGING 0 +#define OVR_HAP_LOGGING 0 + +#define LOCTEXT_NAMESPACE "OculusXRInput" + +static TAutoConsoleVariable<int32> CVarOculusPCMBatchDuration( + TEXT("r.Mobile.Oculus.PCMBatchDuration"), + 36, + TEXT("The duration that each PCM haptic batch lasts in ms. Default is 36ms.\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +static TAutoConsoleVariable<int32> CVarOculusControllerPose( + TEXT("r.Oculus.ControllerPose"), + 0, + TEXT("0 Default controller pose.\n") + TEXT("1 Legacy controller pose.\n") + TEXT("2 Grip controller pose.\n") + TEXT("3 Aim controller pose.\n"), + ECVF_Scalability | ECVF_RenderThreadSafe); + +namespace OculusXRInput +{ + +const FKey FOculusKey::OculusTouch_Left_Thumbstick("OculusTouch_Left_Thumbstick"); +const FKey FOculusKey::OculusTouch_Left_Trigger("OculusTouch_Left_Trigger"); +const FKey FOculusKey::OculusTouch_Left_FaceButton1("OculusTouch_Left_FaceButton1"); +const FKey FOculusKey::OculusTouch_Left_FaceButton2("OculusTouch_Left_FaceButton2"); +const FKey FOculusKey::OculusTouch_Left_IndexPointing("OculusTouch_Left_IndexPointing"); +const FKey FOculusKey::OculusTouch_Left_ThumbUp("OculusTouch_Left_ThumbUp"); +const FKey FOculusKey::OculusTouch_Left_ThumbRest("OculusTouch_Left_ThumbRest"); +const FKey FOculusKey::OculusTouch_Left_ThumbRest_Force("OculusTouch_Left_ThumbRest_Force"); +const FKey FOculusKey::OculusTouch_Left_Stylus_Force("OculusTouch_Left_Stylus_Force"); +const FKey FOculusKey::OculusTouch_Left_IndexTrigger_Curl("OculusTouch_Left_IndexTrigger_Curl"); +const FKey FOculusKey::OculusTouch_Left_IndexTrigger_Slide("OculusTouch_Left_IndexTrigger_Slide"); + +const FKey FOculusKey::OculusTouch_Right_Thumbstick("OculusTouch_Right_Thumbstick"); +const FKey FOculusKey::OculusTouch_Right_Trigger("OculusTouch_Right_Trigger"); +const FKey FOculusKey::OculusTouch_Right_FaceButton1("OculusTouch_Right_FaceButton1"); +const FKey FOculusKey::OculusTouch_Right_FaceButton2("OculusTouch_Right_FaceButton2"); +const FKey FOculusKey::OculusTouch_Right_IndexPointing("OculusTouch_Right_IndexPointing"); +const FKey FOculusKey::OculusTouch_Right_ThumbUp("OculusTouch_Right_ThumbUp"); +const FKey FOculusKey::OculusTouch_Right_ThumbRest("OculusTouch_Right_ThumbRest"); + +const FKey FOculusKey::OculusTouch_Right_ThumbRest_Force("OculusTouch_Right_ThumbRest_Force"); +const FKey FOculusKey::OculusTouch_Right_Stylus_Force("OculusTouch_Right_Stylus_Force"); +const FKey FOculusKey::OculusTouch_Right_IndexTrigger_Curl("OculusTouch_Right_IndexTrigger_Curl"); +const FKey FOculusKey::OculusTouch_Right_IndexTrigger_Slide("OculusTouch_Right_IndexTrigger_Slide"); + +const FKey FOculusKey::OculusRemote_DPad_Down("OculusRemote_DPad_Down"); +const FKey FOculusKey::OculusRemote_DPad_Up("OculusRemote_DPad_Up"); +const FKey FOculusKey::OculusRemote_DPad_Left("OculusRemote_DPad_Left"); +const FKey FOculusKey::OculusRemote_DPad_Right("OculusRemote_DPad_Right"); +const FKey FOculusKey::OculusRemote_Enter("OculusRemote_Enter"); +const FKey FOculusKey::OculusRemote_Back("OculusRemote_Back"); +const FKey FOculusKey::OculusRemote_VolumeUp("OculusRemote_VolumeUp"); +const FKey FOculusKey::OculusRemote_VolumeDown("OculusRemote_VolumeDown"); +const FKey FOculusKey::OculusRemote_Home("OculusRemote_Home"); + +const FKey FOculusKey::OculusHand_Left_ThumbPinch("OculusHand_Left_ThumbPinch"); +const FKey FOculusKey::OculusHand_Left_IndexPinch("OculusHand_Left_IndexPinch"); +const FKey FOculusKey::OculusHand_Left_MiddlePinch("OculusHand_Left_MiddlePinch"); +const FKey FOculusKey::OculusHand_Left_RingPinch("OculusHand_Left_RingPinch"); +const FKey FOculusKey::OculusHand_Left_PinkyPinch("OculusHand_Left_PinkPinch"); + +const FKey FOculusKey::OculusHand_Right_ThumbPinch("OculusHand_Right_ThumbPinch"); +const FKey FOculusKey::OculusHand_Right_IndexPinch("OculusHand_Right_IndexPinch"); +const FKey FOculusKey::OculusHand_Right_MiddlePinch("OculusHand_Right_MiddlePinch"); +const FKey FOculusKey::OculusHand_Right_RingPinch("OculusHand_Right_RingPinch"); +const FKey FOculusKey::OculusHand_Right_PinkyPinch("OculusHand_Right_PinkPinch"); + +const FKey FOculusKey::OculusHand_Left_SystemGesture("OculusHand_Left_SystemGesture"); +const FKey FOculusKey::OculusHand_Right_SystemGesture("OculusHand_Right_SystemGesture"); + +const FKey FOculusKey::OculusHand_Left_ThumbPinchStrength("OculusHand_Left_ThumbPinchStrength"); +const FKey FOculusKey::OculusHand_Left_IndexPinchStrength("OculusHand_Left_IndexPinchStrength"); +const FKey FOculusKey::OculusHand_Left_MiddlePinchStrength("OculusHand_Left_MiddlePinchStrength"); +const FKey FOculusKey::OculusHand_Left_RingPinchStrength("OculusHand_Left_RingPinchStrength"); +const FKey FOculusKey::OculusHand_Left_PinkyPinchStrength("OculusHand_Left_PinkPinchStrength"); + +const FKey FOculusKey::OculusHand_Right_ThumbPinchStrength("OculusHand_Right_ThumbPinchStrength"); +const FKey FOculusKey::OculusHand_Right_IndexPinchStrength("OculusHand_Right_IndexPinchStrength"); +const FKey FOculusKey::OculusHand_Right_MiddlePinchStrength("OculusHand_Right_MiddlePinchStrength"); +const FKey FOculusKey::OculusHand_Right_RingPinchStrength("OculusHand_Right_RingPinchStrength"); +const FKey FOculusKey::OculusHand_Right_PinkyPinchStrength("OculusHand_Right_PinkPinchStrength"); + +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_Thumbstick("OculusTouch_Left_Thumbstick"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_Trigger("OculusTouch_Left_Trigger"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_FaceButton1("OculusTouch_Left_FaceButton1"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_FaceButton2("OculusTouch_Left_FaceButton2"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_IndexPointing("OculusTouch_Left_IndexPointing"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_ThumbUp("OculusTouch_Left_ThumbUp"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Left_ThumbRest("OculusTouch_Left_ThumbRest"); + +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_Thumbstick("OculusTouch_Right_Thumbstick"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_Trigger("OculusTouch_Right_Trigger"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_FaceButton1("OculusTouch_Right_FaceButton1"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_FaceButton2("OculusTouch_Right_FaceButton2"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_IndexPointing("OculusTouch_Right_IndexPointing"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_ThumbUp("OculusTouch_Right_ThumbUp"); +const FOculusKeyNames::Type FOculusKeyNames::OculusTouch_Right_ThumbRest("OculusTouch_Right_ThumbRest"); + +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_DPad_Down("OculusRemote_DPad_Down"); +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_DPad_Up("OculusRemote_DPad_Up"); +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_DPad_Left("OculusRemote_DPad_Left"); +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_DPad_Right("OculusRemote_DPad_Right"); +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_Enter("OculusRemote_Enter"); +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_Back("OculusRemote_Back"); +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_VolumeUp("OculusRemote_VolumeUp"); +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_VolumeDown("OculusRemote_VolumeDown"); +const FOculusKeyNames::Type FOculusKeyNames::OculusRemote_Home("OculusRemote_Home"); + +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_ThumbPinch("OculusHand_Left_ThumbPinch"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_IndexPinch("OculusHand_Left_IndexPinch"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_MiddlePinch("OculusHand_Left_MiddlePinch"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_RingPinch("OculusHand_Left_RingPinch"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_PinkyPinch("OculusHand_Left_PinkPinch"); + +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_ThumbPinch("OculusHand_Right_ThumbPinch"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_IndexPinch("OculusHand_Right_IndexPinch"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_MiddlePinch("OculusHand_Right_MiddlePinch"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_RingPinch("OculusHand_Right_RingPinch"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_PinkyPinch("OculusHand_Right_PinkPinch"); + +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_SystemGesture("OculusHand_Left_SystemGesture"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_SystemGesture("OculusHand_Right_SystemGesture"); + +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_ThumbPinchStrength("OculusHand_Left_ThumbPinchStrength"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_IndexPinchStrength("OculusHand_Left_IndexPinchStrength"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_MiddlePinchStrength("OculusHand_Left_MiddlePinchStrength"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_RingPinchStrength("OculusHand_Left_RingPinchStrength"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Left_PinkyPinchStrength("OculusHand_Left_PinkPinchStrength"); + +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_ThumbPinchStrength("OculusHand_Right_ThumbPinchStrength"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_IndexPinchStrength("OculusHand_Right_IndexPinchStrength"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_MiddlePinchStrength("OculusHand_Right_MiddlePinchStrength"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_RingPinchStrength("OculusHand_Right_RingPinchStrength"); +const FOculusKeyNames::Type FOculusKeyNames::OculusHand_Right_PinkyPinchStrength("OculusHand_Right_PinkPinchStrength"); + +/** Threshold for treating trigger pulls as button presses, from 0.0 to 1.0 */ +float FOculusXRInput::TriggerThreshold = 0.8f; + +/** Are Remote keys mapped to gamepad or not. */ +bool FOculusXRInput::bRemoteKeysMappedToGamepad = true; + +float FOculusXRInput::InitialButtonRepeatDelay = 0.2f; +float FOculusXRInput::ButtonRepeatDelay = 0.1f; +bool FOculusXRInput::bPulledHapticsDesc = false; + +FOculusXRInput::FOculusXRInput( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) + : OVRPluginHandle(nullptr) + , MessageHandler( InMessageHandler ) + , ControllerPairs() +{ + // take care of backward compatibility of Remote with Gamepad + if (bRemoteKeysMappedToGamepad) + { + Remote.MapKeysToGamepad(); + } + + OVRPluginHandle = FOculusXRHMDModule::GetOVRPluginHandle(); + + FOculusControllerPair& ControllerPair = *new(ControllerPairs) FOculusControllerPair(); + + // @todo: Unreal controller index should be assigned to us by the engine to ensure we don't contest with other devices + ControllerPair.UnrealControllerIndex = 0; //???? NextUnrealControllerIndex++; + + IModularFeatures::Get().RegisterModularFeature( GetModularFeatureName(), this ); + + LocalTrackingSpaceRecenterCount = 0; + + UE_LOG(LogOcInput, Log, TEXT("OculusXRInput is initialized")); +} + + +FOculusXRInput::~FOculusXRInput() +{ + IModularFeatures::Get().UnregisterModularFeature( GetModularFeatureName(), this ); + + if (OVRPluginHandle) + { + FPlatformProcess::FreeDllHandle(OVRPluginHandle); + OVRPluginHandle = nullptr; + } +} + +void FOculusXRInput::PreInit() +{ + // Load the config, even if we failed to initialize a controller + LoadConfig(); + + // Register the FKeys + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_Thumbstick, LOCTEXT("OculusTouch_Left_Thumbstick", "Oculus Touch (L) Thumbstick CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D)); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_FaceButton1, LOCTEXT("OculusTouch_Left_FaceButton1", "Oculus Touch (L) X Button CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D)); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_Trigger, LOCTEXT("OculusTouch_Left_Trigger", "Oculus Touch (L) Trigger CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D)); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_FaceButton2, LOCTEXT("OculusTouch_Left_FaceButton2", "Oculus Touch (L) Y Button CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D)); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_IndexPointing, LOCTEXT("OculusTouch_Left_IndexPointing", "Oculus Touch (L) Pointing CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_ThumbUp, LOCTEXT("OculusTouch_Left_ThumbUp", "Oculus Touch (L) Thumb Up CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_ThumbRest, LOCTEXT("OculusTouch_Left_ThumbRest", "Oculus Touch (L) Thumb Rest CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_ThumbRest_Force, LOCTEXT("OculusTouch_Left_ThumbRest_Force", "Oculus Touch (L) Thumb Rest Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_Stylus_Force, LOCTEXT("OculusTouch_Left_Stylus_Force", "Oculus Touch (L) Stylus Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_IndexTrigger_Curl, LOCTEXT("OculusTouch_Left_IndexTrigger_Curl", "Oculus Touch (L) Trigger Curl CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Left_IndexTrigger_Slide, LOCTEXT("OculusTouch_Left_IndexTrigger_Slide", "Oculus Touch (L) Trigger Slide CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_Thumbstick, LOCTEXT("OculusTouch_Right_Thumbstick", "Oculus Touch (R) Thumbstick CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey)); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_FaceButton1, LOCTEXT("OculusTouch_Right_FaceButton1", "Oculus Touch (R) A Button CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey)); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_Trigger, LOCTEXT("OculusTouch_Right_Trigger", "Oculus Touch (R) Trigger CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey)); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_FaceButton2, LOCTEXT("OculusTouch_Right_FaceButton2", "Oculus Touch (R) B Button CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey)); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_IndexPointing, LOCTEXT("OculusTouch_Right_IndexPointing", "Oculus Touch (R) Pointing CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_ThumbUp, LOCTEXT("OculusTouch_Right_ThumbUp", "Oculus Touch (R) Thumb Up CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_ThumbRest, LOCTEXT("OculusTouch_Right_ThumbRest", "Oculus Touch (R) Thumb Rest CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_ThumbRest_Force, LOCTEXT("OculusTouch_Right_ThumbRest_Force", "Oculus Touch (R) Thumb Rest Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_Stylus_Force, LOCTEXT("OculusTouch_Right_Stylus_Force", "Oculus Touch (R) Stylus Force"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_IndexTrigger_Curl, LOCTEXT("OculusTouch_Right_IndexTrigger_Curl", "Oculus Touch (R) Trigger Curl CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusTouch_Right_IndexTrigger_Slide, LOCTEXT("OculusTouch_Right_IndexTrigger_Slide", "Oculus Touch (R) Trigger Slide CapTouch"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D | FKeyDetails::NotBlueprintBindableKey, "OculusTouch")); + + EKeys::AddMenuCategoryDisplayInfo("OculusRemote", LOCTEXT("OculusRemoteSubCategory", "Oculus Remote"), TEXT("GraphEditor.PadEvent_16x")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_DPad_Up, LOCTEXT("OculusRemote_DPad_Up", "Oculus Remote D-pad Up"), FKeyDetails::GamepadKey, "OculusRemote")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_DPad_Down, LOCTEXT("OculusRemote_DPad_Down", "Oculus Remote D-pad Down"), FKeyDetails::GamepadKey, "OculusRemote")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_DPad_Left, LOCTEXT("OculusRemote_DPad_Left", "Oculus Remote D-pad Left"), FKeyDetails::GamepadKey, "OculusRemote")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_DPad_Right, LOCTEXT("OculusRemote_DPad_Right", "Oculus Remote D-pad Right"), FKeyDetails::GamepadKey, "OculusRemote")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_Enter, LOCTEXT("OculusRemote_Enter", "Oculus Remote Enter"), FKeyDetails::GamepadKey, "OculusRemote")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_Back, LOCTEXT("OculusRemote_Back", "Oculus Remote Back"), FKeyDetails::GamepadKey, "OculusRemote")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_VolumeUp, LOCTEXT("OculusRemote_VolumeUp", "Oculus Remote Volume Up"), FKeyDetails::GamepadKey, "OculusRemote")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_VolumeDown, LOCTEXT("OculusRemote_VolumeDown", "Oculus Remote Volume Down"), FKeyDetails::GamepadKey, "OculusRemote")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusRemote_Home, LOCTEXT("OculusRemote_Home", "Oculus Remote Home"), FKeyDetails::GamepadKey, "OculusRemote")); + + EKeys::AddMenuCategoryDisplayInfo("OculusHand", LOCTEXT("OculusHandSubCategory", "Oculus Hand"), TEXT("GraphEditor.PadEvent_16x")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_ThumbPinch, LOCTEXT("OculusHand_Left_ThumbPinch", "Oculus Hand (L) Thumb Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_IndexPinch, LOCTEXT("OculusHand_Left_IndexPinch", "Oculus Hand (L) Index Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_MiddlePinch, LOCTEXT("OculusHand_Left_MiddlePinch", "Oculus Hand (L) Middle Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_RingPinch, LOCTEXT("OculusHand_Left_RingPinch", "Oculus Hand (L) Ring Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_PinkyPinch, LOCTEXT("OculusHand_Left_PinkyPinch", "Oculus Hand (L) Pinky Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_ThumbPinch, LOCTEXT("OculusHand_Right_ThumbPinch", "Oculus Hand (R) Thumb Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_IndexPinch, LOCTEXT("OculusHand_Right_IndexPinch", "Oculus Hand (R) Index Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_MiddlePinch, LOCTEXT("OculusHand_Right_MiddlePinch", "Oculus Hand (R) Middle Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_RingPinch, LOCTEXT("OculusHand_Right_RingPinch", "Oculus Hand (R) Ring Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_PinkyPinch, LOCTEXT("OculusHand_Right_PinkyPinch", "Oculus Hand (R) Pinky Pinch"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_SystemGesture, LOCTEXT("OculusHand_Left_SystemGesture", "Oculus Hand (L) System Gesture"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_SystemGesture, LOCTEXT("OculusHand_Right_SystemGesture", "Oculus Hand (R) System Gesture"), FKeyDetails::GamepadKey | FKeyDetails::NotBlueprintBindableKey, "OculusHand")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_ThumbPinchStrength, LOCTEXT("OculusHand_Left_ThumbPinchStrength", "Oculus Hand (L) Thumb Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_IndexPinchStrength, LOCTEXT("OculusHand_Left_IndexPinchStrength", "Oculus Hand (L) Index Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_MiddlePinchStrength, LOCTEXT("OculusHand_Left_MiddlePinchStrength", "Oculus Hand (L) Middle Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_RingPinchStrength, LOCTEXT("OculusHand_Left_RingPinchStrength", "Oculus Hand (L) Ring Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Left_PinkyPinchStrength, LOCTEXT("OculusHand_Left_PinkyPinchStrength", "Oculus Hand (L) Pinky Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_ThumbPinchStrength, LOCTEXT("OculusHand_Right_ThumbPinchStrength", "Oculus Hand (R) Thumb Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_IndexPinchStrength, LOCTEXT("OculusHand_Right_IndexPinchStrength", "Oculus Hand (R) Index Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_MiddlePinchStrength, LOCTEXT("OculusHand_Right_MiddlePinchStrength", "Oculus Hand (R) Middle Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_RingPinchStrength, LOCTEXT("OculusHand_Right_RingPinchStrength", "Oculus Hand (R) Ring Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + EKeys::AddKey(FKeyDetails(FOculusKey::OculusHand_Right_PinkyPinchStrength, LOCTEXT("OculusHand_Right_PinkyPinchStrength", "Oculus Hand (R) Pinky Pinch Strength"), FKeyDetails::GamepadKey | FKeyDetails::Axis1D, "OculusHand")); + + UE_LOG(LogOcInput, Log, TEXT("OculusXRInput pre-init called")); +} + +void FOculusXRInput::LoadConfig() +{ + const TCHAR* OculusTouchSettings = TEXT("OculusTouch.Settings"); + float ConfigThreshold = TriggerThreshold; + if (GConfig->GetFloat(OculusTouchSettings, TEXT("TriggerThreshold"), ConfigThreshold, GEngineIni)) + { + TriggerThreshold = ConfigThreshold; + } + + const TCHAR* OculusRemoteSettings = TEXT("OculusRemote.Settings"); + bool bConfigRemoteKeysMappedToGamepad; + if (GConfig->GetBool(OculusRemoteSettings, TEXT("bRemoteKeysMappedToGamepad"), bConfigRemoteKeysMappedToGamepad, GEngineIni)) + { + bRemoteKeysMappedToGamepad = bConfigRemoteKeysMappedToGamepad; + } + + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("InitialButtonRepeatDelay"), InitialButtonRepeatDelay, GInputIni); + GConfig->GetFloat(TEXT("/Script/Engine.InputSettings"), TEXT("ButtonRepeatDelay"), ButtonRepeatDelay, GInputIni); +} + +void FOculusXRInput::Tick( float DeltaTime ) +{ + // Nothing to do when ticking, for now. SendControllerEvents() handles everything. +} + + +void FOculusXRInput::SendControllerEvents() +{ + const double CurrentTime = FPlatformTime::Seconds(); + const float AnalogButtonPressThreshold = TriggerThreshold; + float DeltaTime = 0.0; + if (StartTime < CurrentTime) + { + DeltaTime = (float)(CurrentTime - StartTime); + StartTime = CurrentTime; + } + + if(IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && FApp::HasVRFocus()) + { + if (MessageHandler.IsValid() && GEngine->XRSystem->GetHMDDevice()) + { + OculusXRHMD::FOculusXRHMD* OculusXRHMD = static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem->GetHMDDevice()); + OculusXRHMD->StartGameFrame_GameThread(); + + ovrpControllerState5 OvrpControllerState; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetControllerState5(ovrpController_Remote, &OvrpControllerState)) && + (OvrpControllerState.ConnectedControllerTypes & ovrpController_Remote)) + { + for (int32 ButtonIndex = 0; ButtonIndex < (int32)EOculusRemoteControllerButton::TotalButtonCount; ++ButtonIndex) + { + FOculusButtonState& ButtonState = Remote.Buttons[ButtonIndex]; + check(!ButtonState.Key.IsNone()); // is button's name initialized? + + // Determine if the button is pressed down + bool bButtonPressed = false; + switch ((EOculusRemoteControllerButton)ButtonIndex) + { + case EOculusRemoteControllerButton::DPad_Up: + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Up) != 0; + break; + + case EOculusRemoteControllerButton::DPad_Down: + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Down) != 0; + break; + + case EOculusRemoteControllerButton::DPad_Left: + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Left) != 0; + break; + + case EOculusRemoteControllerButton::DPad_Right: + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Right) != 0; + break; + + case EOculusRemoteControllerButton::Enter: + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Start) != 0; + break; + + case EOculusRemoteControllerButton::Back: + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Back) != 0; + break; + + case EOculusRemoteControllerButton::VolumeUp: + #ifdef SUPPORT_INTERNAL_BUTTONS + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_VolUp) != 0; + #endif + break; + + case EOculusRemoteControllerButton::VolumeDown: + #ifdef SUPPORT_INTERNAL_BUTTONS + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_VolDown) != 0; + #endif + break; + + case EOculusRemoteControllerButton::Home: + #ifdef SUPPORT_INTERNAL_BUTTONS + bButtonPressed = (OvrpControllerState.Buttons & ovrpButton_Home) != 0; + #endif + break; + + default: + check(0); // unhandled button, shouldn't happen + break; + } + + // Update button state + if (bButtonPressed != ButtonState.bIsPressed) + { + ButtonState.bIsPressed = bButtonPressed; + if (ButtonState.bIsPressed) + { + OnControllerButtonPressed(ButtonState, 0, false); + + // Set the timer for the first repeat + ButtonState.NextRepeatTime = CurrentTime + InitialButtonRepeatDelay; + } + else + { + OnControllerButtonReleased(ButtonState, 0, false); + } + } + + // Apply key repeat, if its time for that + if (ButtonState.bIsPressed && ButtonState.NextRepeatTime <= CurrentTime) + { + OnControllerButtonPressed(ButtonState, 0, true); + + // Set the timer for the next repeat + ButtonState.NextRepeatTime = CurrentTime + ButtonRepeatDelay; + } + } + } + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetControllerState5((ovrpController)(ovrpController_LTrackedRemote | ovrpController_RTrackedRemote | ovrpController_Touch), &OvrpControllerState))) + { + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: ButtonState = 0x%X"), OvrpControllerState.Buttons); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: Touches = 0x%X"), OvrpControllerState.Touches); + + // If using touch controllers (Quest) use the local tracking space recentering as a signal for recenter + if ((OvrpControllerState.ConnectedControllerTypes & ovrpController_LTouch) != 0 || (OvrpControllerState.ConnectedControllerTypes & ovrpController_RTouch) != 0) + { + int32 recenterCount = 0; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetLocalTrackingSpaceRecenterCount(&recenterCount))) + { + if (LocalTrackingSpaceRecenterCount != recenterCount) + { + FCoreDelegates::VRControllerRecentered.Broadcast(); + LocalTrackingSpaceRecenterCount = recenterCount; + } + } + } + + for (FOculusControllerPair& ControllerPair : ControllerPairs) + { + for (int32 HandIndex = 0; HandIndex < UE_ARRAY_COUNT(ControllerPair.TouchControllerStates); ++HandIndex) + { + FOculusTouchControllerState& State = ControllerPair.TouchControllerStates[HandIndex]; + bool bIsLeft = (HandIndex == (int32)EControllerHand::Left); + + bool bIsMobileController = bIsLeft ? (OvrpControllerState.ConnectedControllerTypes & ovrpController_LTrackedRemote) != 0 : (OvrpControllerState.ConnectedControllerTypes & ovrpController_RTrackedRemote) != 0; + bool bIsTouchController = bIsLeft ? (OvrpControllerState.ConnectedControllerTypes & ovrpController_LTouch) != 0 : (OvrpControllerState.ConnectedControllerTypes & ovrpController_RTouch) != 0; + bool bIsCurrentlyTracked = bIsMobileController || bIsTouchController; + + if (bIsCurrentlyTracked) + { + ovrpNode OvrpNode = (HandIndex == (int32)EControllerHand::Left) ? ovrpNode_HandLeft : ovrpNode_HandRight; + + State.bIsConnected = true; + ovrpBool bResult = true; + State.bIsPositionTracked = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePositionTracked2(OvrpNode, &bResult)) && bResult; + State.bIsPositionValid = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePositionValid(OvrpNode, &bResult)) && bResult; + State.bIsOrientationTracked = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodeOrientationTracked2(OvrpNode, &bResult)) && bResult; + State.bIsOrientationValid = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodeOrientationValid(OvrpNode, &bResult)) && bResult; + + const float OvrTriggerAxis = OvrpControllerState.IndexTrigger[HandIndex]; + const float OvrGripAxis = OvrpControllerState.HandTrigger[HandIndex]; + const float OvrThumbRestForce = OvrpControllerState.ThumbRestForce[HandIndex]; + const float OvrStylusForce = OvrpControllerState.StylusForce[HandIndex]; + const float OvrIndexTriggerCurl = OvrpControllerState.IndexTriggerCurl[HandIndex]; + const float OvrIndexTriggerSlide = OvrpControllerState.IndexTriggerSlide[HandIndex]; + + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: IndexTrigger[%d] = %f"), int(HandIndex), OvrTriggerAxis); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: HandTrigger[%d] = %f"), int(HandIndex), OvrGripAxis); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: ThumbStick[%d] = { %f, %f }"), int(HandIndex), OvrpControllerState.Thumbstick[HandIndex].x, OvrpControllerState.Thumbstick[HandIndex].y); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: ThumbRestForce[%d] = %f"), int(HandIndex), OvrThumbRestForce); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: StylusForce[%d] = %f"), int(HandIndex), OvrStylusForce); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: IndexTriggerCurl[%d] = %f"), int(HandIndex), OvrIndexTriggerCurl); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: IndexTriggerSlide[%d] = %f"), int(HandIndex), OvrIndexTriggerSlide); + if (bIsMobileController) + { + if (OvrpControllerState.RecenterCount[HandIndex] != State.RecenterCount) + { + State.RecenterCount = OvrpControllerState.RecenterCount[HandIndex]; + FCoreDelegates::VRControllerRecentered.Broadcast(); + } + } + + if (OvrTriggerAxis != State.TriggerAxis) + { + State.TriggerAxis = OvrTriggerAxis; + MessageHandler->OnControllerAnalog(bIsLeft ? EKeys::OculusTouch_Left_Trigger_Axis.GetFName() : EKeys::OculusTouch_Right_Trigger_Axis.GetFName(), ControllerPair.UnrealControllerIndex, State.TriggerAxis); + } + + if (OvrGripAxis != State.GripAxis) + { + State.GripAxis = OvrGripAxis; + MessageHandler->OnControllerAnalog(bIsLeft ? EKeys::OculusTouch_Left_Grip_Axis.GetFName() : EKeys::OculusTouch_Right_Grip_Axis.GetFName(), ControllerPair.UnrealControllerIndex, State.GripAxis); + } + + ovrpVector2f ThumbstickValue = OvrpControllerState.Thumbstick[HandIndex]; + ovrpVector2f TouchpadValue = OvrpControllerState.Touchpad[HandIndex]; + + if (ThumbstickValue.x != State.ThumbstickAxes.X) + { + State.ThumbstickAxes.X = ThumbstickValue.x; + MessageHandler->OnControllerAnalog(bIsLeft ? EKeys::OculusTouch_Left_Thumbstick_X.GetFName() : EKeys::OculusTouch_Right_Thumbstick_X.GetFName(), ControllerPair.UnrealControllerIndex, State.ThumbstickAxes.X); + } + + if (ThumbstickValue.y != State.ThumbstickAxes.Y) + { + State.ThumbstickAxes.Y = ThumbstickValue.y; + MessageHandler->OnControllerAnalog(bIsLeft ? EKeys::OculusTouch_Left_Thumbstick_Y.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Y.GetFName(), ControllerPair.UnrealControllerIndex, State.ThumbstickAxes.Y); + } + + if (TouchpadValue.x != State.TouchpadAxes.X) + { + State.TouchpadAxes.X = TouchpadValue.x; + } + + if (TouchpadValue.y != State.TouchpadAxes.Y) + { + State.TouchpadAxes.Y = TouchpadValue.y; + } + + if (OvrThumbRestForce != State.ThumbRestForce) + { + State.ThumbRestForce = OvrThumbRestForce; + MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_ThumbRest_Force.GetFName() : FOculusKey::OculusTouch_Right_ThumbRest_Force.GetFName(), ControllerPair.UnrealControllerIndex, State.ThumbRestForce); + } + + if (OvrStylusForce != State.StylusForce) + { + State.StylusForce = OvrStylusForce; + MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_Stylus_Force.GetFName() : FOculusKey::OculusTouch_Right_Stylus_Force.GetFName(), ControllerPair.UnrealControllerIndex, State.StylusForce); + } + + if (OvrIndexTriggerCurl != State.IndexTriggerCurl) + { + State.IndexTriggerCurl = OvrIndexTriggerCurl; + MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_IndexTrigger_Curl.GetFName() : FOculusKey::OculusTouch_Right_IndexTrigger_Curl.GetFName(), ControllerPair.UnrealControllerIndex, State.IndexTriggerCurl); + } + + if (OvrIndexTriggerSlide != State.IndexTriggerSlide) + { + State.IndexTriggerSlide = OvrIndexTriggerSlide; + MessageHandler->OnControllerAnalog(bIsLeft ? FOculusKey::OculusTouch_Left_IndexTrigger_Slide.GetFName() : FOculusKey::OculusTouch_Right_IndexTrigger_Slide.GetFName(), ControllerPair.UnrealControllerIndex, State.IndexTriggerSlide); + } + + for (int32 ButtonIndex = 0; ButtonIndex < (int32)EOculusTouchControllerButton::TotalButtonCount; ++ButtonIndex) + { + FOculusButtonState& ButtonState = State.Buttons[ButtonIndex]; + check(!ButtonState.Key.IsNone()); // is button's name initialized? + + // Determine if the button is pressed down + bool bButtonPressed = false; + switch ((EOculusTouchControllerButton)ButtonIndex) + { + case EOculusTouchControllerButton::Trigger: + bButtonPressed = State.TriggerAxis >= AnalogButtonPressThreshold; + break; + + case EOculusTouchControllerButton::Grip: + bButtonPressed = State.GripAxis >= AnalogButtonPressThreshold; + break; + + case EOculusTouchControllerButton::XA: + bButtonPressed = bIsLeft ? (OvrpControllerState.Buttons & ovrpButton_X) != 0 : (OvrpControllerState.Buttons & ovrpButton_A) != 0; + break; + + case EOculusTouchControllerButton::YB: + bButtonPressed = bIsLeft ? (OvrpControllerState.Buttons & ovrpButton_Y) != 0 : (OvrpControllerState.Buttons & ovrpButton_B) != 0; + break; + + case EOculusTouchControllerButton::Thumbstick: + bButtonPressed = bIsLeft ? (OvrpControllerState.Buttons & ovrpButton_LThumb) != 0 : (OvrpControllerState.Buttons & ovrpButton_RThumb) != 0; + break; + + case EOculusTouchControllerButton::Thumbstick_Up: + if (bIsTouchController && State.ThumbstickAxes.Size() > 0.7f || + bIsMobileController && State.Buttons[(int)EOculusTouchControllerButton::Thumbstick].bIsPressed && State.ThumbstickAxes.Size() > 0.5f) + { + float Angle = FMath::Atan2(State.ThumbstickAxes.Y, State.ThumbstickAxes.X); + bButtonPressed = Angle >= (1.0f / 8.0f) * PI && Angle <= (7.0f / 8.0f) * PI; + } + break; + + case EOculusTouchControllerButton::Thumbstick_Down: + if (bIsTouchController && State.ThumbstickAxes.Size() > 0.7f || + bIsMobileController && State.Buttons[(int)EOculusTouchControllerButton::Thumbstick].bIsPressed && State.ThumbstickAxes.Size() > 0.5f) + { + float Angle = FMath::Atan2(State.ThumbstickAxes.Y, State.ThumbstickAxes.X); + bButtonPressed = Angle >= (-7.0f / 8.0f) * PI && Angle <= (-1.0f / 8.0f) * PI; + } + break; + + case EOculusTouchControllerButton::Thumbstick_Left: + if (bIsTouchController && State.ThumbstickAxes.Size() > 0.7f || + bIsMobileController && State.Buttons[(int)EOculusTouchControllerButton::Thumbstick].bIsPressed && State.ThumbstickAxes.Size() > 0.5f) + { + float Angle = FMath::Atan2(State.ThumbstickAxes.Y, State.ThumbstickAxes.X); + bButtonPressed = Angle <= (-5.0f / 8.0f) * PI || Angle >= (5.0f / 8.0f) * PI; + } + break; + + case EOculusTouchControllerButton::Thumbstick_Right: + if (bIsTouchController && State.ThumbstickAxes.Size() > 0.7f || + bIsMobileController && State.Buttons[(int)EOculusTouchControllerButton::Thumbstick].bIsPressed && State.ThumbstickAxes.Size() > 0.5f) + { + float Angle = FMath::Atan2(State.ThumbstickAxes.Y, State.ThumbstickAxes.X); + bButtonPressed = Angle >= (-3.0f / 8.0f) * PI && Angle <= (3.0f / 8.0f) * PI; + } + break; + + case EOculusTouchControllerButton::Menu: + bButtonPressed = bIsLeft && (OvrpControllerState.Buttons & ovrpButton_Start); + break; + + case EOculusTouchControllerButton::Thumbstick_Touch: + bButtonPressed = bIsLeft ? (OvrpControllerState.Touches & ovrpTouch_LThumb) != 0 : (OvrpControllerState.Touches & ovrpTouch_RThumb) != 0; + break; + + case EOculusTouchControllerButton::Trigger_Touch: + bButtonPressed = bIsLeft ? (OvrpControllerState.Touches & ovrpTouch_LIndexTrigger) != 0 : (OvrpControllerState.Touches & ovrpTouch_RIndexTrigger) != 0; + break; + + case EOculusTouchControllerButton::XA_Touch: + bButtonPressed = bIsLeft ? (OvrpControllerState.Touches & ovrpTouch_X) != 0 : (OvrpControllerState.Touches & ovrpTouch_A) != 0; + break; + + case EOculusTouchControllerButton::YB_Touch: + bButtonPressed = bIsLeft ? (OvrpControllerState.Touches & ovrpTouch_Y) != 0 : (OvrpControllerState.Touches & ovrpTouch_B) != 0; + break; + + default: + check(0); + break; + } + + // Update button state + if (bButtonPressed != ButtonState.bIsPressed) + { + ButtonState.bIsPressed = bButtonPressed; + if (ButtonState.bIsPressed) + { + OnControllerButtonPressed(ButtonState, ControllerPair.UnrealControllerIndex, false); + + // Set the timer for the first repeat + ButtonState.NextRepeatTime = CurrentTime + InitialButtonRepeatDelay; + } + else + { + OnControllerButtonReleased(ButtonState, ControllerPair.UnrealControllerIndex, false); + } + } + + // Apply key repeat, if its time for that + if (ButtonState.bIsPressed && ButtonState.NextRepeatTime <= CurrentTime) + { + OnControllerButtonPressed(ButtonState, ControllerPair.UnrealControllerIndex, true); + + // Set the timer for the next repeat + ButtonState.NextRepeatTime = CurrentTime + ButtonRepeatDelay; + } + } + + // Handle Capacitive States + for (int32 CapTouchIndex = 0; CapTouchIndex < (int32)EOculusTouchCapacitiveAxes::TotalAxisCount; ++CapTouchIndex) + { + FOculusAxisState& CapState = State.CapacitiveAxes[CapTouchIndex]; + + float CurrentAxisVal = 0.f; + switch ((EOculusTouchCapacitiveAxes)CapTouchIndex) + { + case EOculusTouchCapacitiveAxes::XA: + { + const uint32 mask = (bIsLeft) ? ovrpTouch_X : ovrpTouch_A; + CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; + break; + } + case EOculusTouchCapacitiveAxes::YB: + { + const uint32 mask = (bIsLeft) ? ovrpTouch_Y : ovrpTouch_B; + CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; + break; + } + case EOculusTouchCapacitiveAxes::Thumbstick: + { + const uint32 mask = bIsMobileController ? ((bIsLeft) ? ovrpTouch_LTouchpad : ovrpTouch_RTouchpad) : ((bIsLeft) ? ovrpTouch_LThumb : ovrpTouch_RThumb); + CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; + break; + } + case EOculusTouchCapacitiveAxes::Trigger: + { + const uint32 mask = (bIsLeft) ? ovrpTouch_LIndexTrigger : ovrpTouch_RIndexTrigger; + CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; + break; + } + case EOculusTouchCapacitiveAxes::IndexPointing: + { + const uint32 mask = (bIsLeft) ? ovrpNearTouch_LIndexTrigger : ovrpNearTouch_RIndexTrigger; + CurrentAxisVal = (OvrpControllerState.NearTouches & mask) != 0 ? 0.f : 1.f; + break; + } + case EOculusTouchCapacitiveAxes::ThumbUp: + { + const uint32 mask = (bIsLeft) ? ovrpNearTouch_LThumbButtons : ovrpNearTouch_RThumbButtons; + CurrentAxisVal = (OvrpControllerState.NearTouches & mask) != 0 ? 0.f : 1.f; + break; + } + case EOculusTouchCapacitiveAxes::ThumbRest: + { + const uint32 mask = (bIsLeft) ? ovrpTouch_LThumbRest : ovrpTouch_RThumbRest; + CurrentAxisVal = (OvrpControllerState.Touches & mask) != 0 ? 1.f : 0.f; + break; + } + default: + check(0); + } + + if (CurrentAxisVal != CapState.State) + { + MessageHandler->OnControllerAnalog(CapState.Axis, ControllerPair.UnrealControllerIndex, CurrentAxisVal); + + CapState.State = CurrentAxisVal; + } + } + ProcessHaptics(DeltaTime); + } + else + { + // Controller isn't available right now. Zero out input state, so that if it comes back it will send fresh event deltas + State = FOculusTouchControllerState((EControllerHand)HandIndex); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: Controller for the hand %d is not tracked"), int(HandIndex)); + } + } + } + } + else + { + // Controller isn't available right now. Zero out input state, so that if it comes back it will send fresh event deltas + for (FOculusControllerPair& ControllerPair : ControllerPairs) + { + for (int32 HandIndex = 0; HandIndex < UE_ARRAY_COUNT(ControllerPair.TouchControllerStates); ++HandIndex) + { + FOculusTouchControllerState& State = ControllerPair.TouchControllerStates[HandIndex]; + State = FOculusTouchControllerState((EControllerHand)HandIndex); + } + } + } + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetControllerState5((ovrpController)(ovrpController_LHand | ovrpController_RHand | ovrpController_Hands), &OvrpControllerState))) + { + for (FOculusControllerPair& ControllerPair : ControllerPairs) + { + for (int32 HandIndex = 0; HandIndex < UE_ARRAY_COUNT(ControllerPair.HandControllerStates); ++HandIndex) + { + FOculusHandControllerState& State = ControllerPair.HandControllerStates[HandIndex]; + + bool bIsLeft = (HandIndex == (int32)EControllerHand::Left); + bool bIsCurrentlyTracked = bIsLeft ? (OvrpControllerState.ConnectedControllerTypes & ovrpController_LHand) != 0 : (OvrpControllerState.ConnectedControllerTypes & ovrpController_RHand) != 0; + + if (bIsCurrentlyTracked) + { + State.bIsConnected = true; + ovrpBool bResult = true; + + // Hand Tracking requires the frame number for accurate results + OculusXRHMD::FGameFrame* CurrentFrame; + if (IsInGameThread()) + { + CurrentFrame = OculusXRHMD->GetNextFrameToRender(); + } + else + { + CurrentFrame = OculusXRHMD->GetFrame_RenderThread(); + } + + // Poll for Hand Tracking State + ovrpHandState HandState; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetHandState2(ovrpStep_Render, CurrentFrame ? CurrentFrame->FrameNumber : OVRP_CURRENT_FRAMEINDEX, (ovrpHand)HandIndex, &HandState))) + { + // Update various data about hands + State.HandScale = HandState.HandScale; + + // Update Bone Rotations + for (uint32 BoneIndex = 0; BoneIndex < UE_ARRAY_COUNT(State.BoneRotations); BoneIndex++) + { + ovrpQuatf RawRotation = HandState.BoneRotations[BoneIndex]; + FQuat BoneRotation = FOculusHandTracking::OvrBoneQuatToFQuat(RawRotation); + BoneRotation.Normalize(); + State.BoneRotations[BoneIndex] = BoneRotation; + } + + // Update Pinch State and Pinch Strength + bool bTracked = (HandState.Status & ovrpHandStatus_HandTracked) != 0; + State.TrackingConfidence = FOculusHandTracking::ToEOculusXRTrackingConfidence(HandState.HandConfidence); + + State.bIsPositionTracked = bTracked && State.TrackingConfidence == EOculusXRTrackingConfidence::High; + State.bIsPositionValid = bTracked; + State.bIsOrientationTracked = bTracked && State.TrackingConfidence == EOculusXRTrackingConfidence::High; + State.bIsOrientationValid = bTracked; + + State.bIsPointerPoseValid = (HandState.Status & ovrpHandStatus_InputValid) != 0; + + ovrpPosef PointerPose = HandState.PointerPose; + State.PointerPose.SetTranslation(OculusXRHMD::ToFVector(PointerPose.Position)); + State.PointerPose.SetRotation(OculusXRHMD::ToFQuat(PointerPose.Orientation)); + + State.bIsDominantHand = (HandState.Status & ovrpHandStatus_DominantHand) != 0; + + // Poll for finger confidence + for (uint32 FingerIndex = 0; FingerIndex < (int32)EOculusHandAxes::TotalAxisCount; FingerIndex++) + { + State.FingerConfidences[FingerIndex] = FOculusHandTracking::ToEOculusXRTrackingConfidence(HandState.FingerConfidences[FingerIndex]); + } + + // Poll for finger pinches + for (uint32 FingerIndex = 0; FingerIndex < (uint32)EOculusHandButton::TotalButtonCount; FingerIndex++) + { + FOculusButtonState& PinchState = State.HandButtons[FingerIndex]; + check(!PinchState.Key.IsNone()); + + bool bPressed = false; + if (FingerIndex < (uint32)EOculusHandButton::System) + { + bPressed = (((uint32)HandState.Pinches & (1 << FingerIndex)) != 0); + bPressed &= (HandState.HandConfidence == ovrpTrackingConfidence_High) && (HandState.FingerConfidences[FingerIndex] == ovrpTrackingConfidence_High); + } + else if(FingerIndex == (uint32)EOculusHandButton::System) + { + bPressed = (HandState.Status & ovrpHandStatus_SystemGestureInProgress) != 0; + } + else + { + bPressed = (OvrpControllerState.Buttons & ovrpButton_Start) != 0 && !State.bIsDominantHand; + } + + if (bPressed != PinchState.bIsPressed) + { + PinchState.bIsPressed = bPressed; + if (PinchState.bIsPressed) + { + OnControllerButtonPressed(PinchState, ControllerPair.UnrealControllerIndex, false); + } + else + { + OnControllerButtonReleased(PinchState, ControllerPair.UnrealControllerIndex, false); + } + } + } + + // Poll for finger strength + for (uint32 FingerIndex = 0; FingerIndex < (uint32)EOculusHandAxes::TotalAxisCount; FingerIndex++) + { + FOculusAxisState& PinchStrength = State.HandAxes[FingerIndex]; + check(!PinchStrength.Axis.IsNone()); + + float PinchValue = 0.0f; + if (HandState.HandConfidence == ovrpTrackingConfidence_High) + { + PinchValue = HandState.PinchStrength[FingerIndex]; + } + + if (PinchValue != PinchStrength.State) + { + MessageHandler->OnControllerAnalog(PinchStrength.Axis, ControllerPair.UnrealControllerIndex, PinchValue); + PinchStrength.State = PinchValue; + } + } + } + } + else + { + State = FOculusHandControllerState((EControllerHand)HandIndex); + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("SendControllerEvents: Hand for the hand %d is not tracked"), int32(HandIndex)); + } + } + } + } + else + { + // Hands are not availble right now, zero out the hand state + for (FOculusControllerPair& ControllerPair : ControllerPairs) + { + for (int32 HandIndex = 0; HandIndex < UE_ARRAY_COUNT(ControllerPair.HandControllerStates); ++HandIndex) + { + FOculusHandControllerState& State = ControllerPair.HandControllerStates[HandIndex]; + State = FOculusHandControllerState((EControllerHand)HandIndex); + } + } + } + } + } + UE_CLOG(OVR_DEBUG_LOGGING, LogOcInput, Log, TEXT("")); +} + + +void FOculusXRInput::SetMessageHandler( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) +{ + MessageHandler = InMessageHandler; +} + + +bool FOculusXRInput::Exec( UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar ) +{ + // No exec commands supported, for now. + return false; +} + +void FOculusXRInput::SetChannelValue( int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value ) +{ + const EControllerHand Hand = ( ChannelType == FForceFeedbackChannelType::LEFT_LARGE || ChannelType == FForceFeedbackChannelType::LEFT_SMALL ) ? EControllerHand::Left : EControllerHand::Right; + + for( FOculusControllerPair& ControllerPair : ControllerPairs ) + { + if( ControllerPair.UnrealControllerIndex == ControllerId ) + { + FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[ (int32)Hand ]; + + if (ControllerState.bPlayingHapticEffect) + { + continue; + } + + // @todo: The SMALL channel controls frequency, the LARGE channel controls amplitude. This is a bit of a weird fit. + if( ChannelType == FForceFeedbackChannelType::LEFT_SMALL || ChannelType == FForceFeedbackChannelType::RIGHT_SMALL ) + { + ControllerState.ForceFeedbackHapticFrequency = Value; + } + else + { + ControllerState.ForceFeedbackHapticAmplitude = Value; + } + + UpdateForceFeedback( ControllerPair, Hand ); + + break; + } + } +} + +void FOculusXRInput::SetChannelValues( int32 ControllerId, const FForceFeedbackValues& Values ) +{ + for( FOculusControllerPair& ControllerPair : ControllerPairs ) + { + if( ControllerPair.UnrealControllerIndex == ControllerId ) + { + // @todo: The SMALL channel controls frequency, the LARGE channel controls amplitude. This is a bit of a weird fit. + FOculusTouchControllerState& LeftControllerState = ControllerPair.TouchControllerStates[ (int32)EControllerHand::Left ]; + if (!LeftControllerState.bPlayingHapticEffect) + { + LeftControllerState.ForceFeedbackHapticFrequency = Values.LeftSmall; + LeftControllerState.ForceFeedbackHapticAmplitude = Values.LeftLarge; + UpdateForceFeedback(ControllerPair, EControllerHand::Left); + } + + FOculusTouchControllerState& RightControllerState = ControllerPair.TouchControllerStates[(int32)EControllerHand::Right]; + if (!RightControllerState.bPlayingHapticEffect) + { + RightControllerState.ForceFeedbackHapticFrequency = Values.RightSmall; + RightControllerState.ForceFeedbackHapticAmplitude = Values.RightLarge; + UpdateForceFeedback(ControllerPair, EControllerHand::Right); + } + } + } +} + +bool FOculusXRInput::SupportsForceFeedback(int32 ControllerId) +{ + for (FOculusControllerPair& ControllerPair : ControllerPairs) + { + if (ControllerPair.UnrealControllerIndex == ControllerId) + { + const FOculusTouchControllerState& ControllerStateLeft = ControllerPair.TouchControllerStates[(int32)EControllerHand::Left]; + const FOculusTouchControllerState& ControllerStateRight = ControllerPair.TouchControllerStates[(int32)EControllerHand::Right]; + + if (!(ControllerStateLeft.bIsConnected || ControllerStateRight.bIsConnected)) + { + // neither hand connected, won't be receiving force feedback + continue; + } + + if (IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + // available so could receive feedback + return true; + } + } + } + + // not handling force feedback + return false; +} + +void FOculusXRInput::UpdateForceFeedback( const FOculusControllerPair& ControllerPair, const EControllerHand Hand ) +{ + const FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[ (int32)Hand ]; + + if( ControllerState.bIsConnected && !ControllerState.bPlayingHapticEffect) + { + if(IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && FApp::HasVRFocus()) + { + ovrpControllerState5 OvrpControllerState; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetControllerState5((ovrpController)(ovrpController_Active | ovrpController_LTrackedRemote | ovrpController_RTrackedRemote), &OvrpControllerState)) && + (OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch | ovrpController_LTrackedRemote | ovrpController_RTrackedRemote))) + { + float FreqMin, FreqMax = 0.f; + GetHapticFrequencyRange(FreqMin, FreqMax); + + // Map the [0.0 - 1.0] range to a useful range of frequencies for the Oculus controllers + const float ActualFrequency = FMath::Lerp(FreqMin, FreqMax, FMath::Clamp(ControllerState.ForceFeedbackHapticFrequency, 0.0f, 1.0f)); + + // Oculus SDK wants amplitude values between 0.0 and 1.0 + const float ActualAmplitude = ControllerState.ForceFeedbackHapticAmplitude * GetHapticAmplitudeScale(); + + ovrpController OvrController = ovrpController_None; + if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch)) + { + OvrController = ( Hand == EControllerHand::Left ) ? ovrpController_LTouch : ovrpController_RTouch; + } + else if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_LTrackedRemote | ovrpController_RTrackedRemote)) + { + OvrController = ( Hand == EControllerHand::Left ) ? ovrpController_LTrackedRemote : ovrpController_RTrackedRemote; + } + + static float LastAmplitudeSent = -1; + if (ActualAmplitude != LastAmplitudeSent) + { + ovrpHapticsLocation hapticsLocationMask = ovrpHapticsLocation::ovrpHapticsLocation_Hand; + FOculusXRHMDModule::GetPluginWrapper().SetControllerLocalizedVibration(OvrController, hapticsLocationMask, ActualFrequency, ActualAmplitude); + LastAmplitudeSent = ActualAmplitude; + } + } + } + } +} + +bool FOculusXRInput::OnControllerButtonPressed(const FOculusButtonState& ButtonState, int32 ControllerId, bool IsRepeat) +{ + bool result = MessageHandler->OnControllerButtonPressed(ButtonState.Key, ControllerId, IsRepeat); + + if (!ButtonState.EmulatedKey.IsNone()) + { + MessageHandler->OnControllerButtonPressed(ButtonState.EmulatedKey, ControllerId, IsRepeat); + } + + return result; +} + +bool FOculusXRInput::OnControllerButtonReleased(const FOculusButtonState& ButtonState, int32 ControllerId, bool IsRepeat) +{ + bool result = MessageHandler->OnControllerButtonReleased(ButtonState.Key, ControllerId, IsRepeat); + + if (!ButtonState.EmulatedKey.IsNone()) + { + MessageHandler->OnControllerButtonReleased(ButtonState.EmulatedKey, ControllerId, IsRepeat); + } + + return result; +} + +FName FOculusXRInput::GetMotionControllerDeviceTypeName() const +{ + const static FName DefaultName(TEXT("OculusXRInputDevice")); + return DefaultName; +} + +struct MotionSourceInfo +{ + FName MotionSource; + EControllerHand ControllerHand; + ovrpNode OvrpNode; +}; + +const TMap<FName, MotionSourceInfo> MotionSourceMap +{ + {FName("Left"), {FName("Left"), EControllerHand::Left, ovrpNode_HandLeft}}, + {FName("Right"), {FName("Right"), EControllerHand::Right, ovrpNode_HandRight}}, + {FName("LeftGrip"), {FName("LeftGrip"), EControllerHand::Left, ovrpNode_HandLeft}}, + {FName("RightGrip"), {FName("RightGrip"), EControllerHand::Right, ovrpNode_HandRight}}, + {FName("LeftAim"), {FName("LeftAim"), EControllerHand::Left, ovrpNode_HandLeft}}, + {FName("RightAim"), {FName("RightAim"), EControllerHand::Right, ovrpNode_HandRight}} +}; + +bool FOculusXRInput::GetControllerOrientationAndPosition(const int32 ControllerIndex, const FName MotionSource, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const +{ + // Don't do renderthread pose update if MRC is active due to controller jitter issues with SceneCaptures + if (IsInGameThread() || !UOculusXRMRFunctionLibrary::IsMrcActive()) + { + for (const FOculusControllerPair& ControllerPair : ControllerPairs) + { + if (ControllerPair.UnrealControllerIndex == ControllerIndex) + { + if (MotionSourceMap.Contains(MotionSource)) + { + if (IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + OculusXRHMD::FOculusXRHMD* OculusXRHMD = static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem->GetHMDDevice()); + ovrpNode Node = MotionSourceMap[MotionSource].OvrpNode; + + ovrpBool bResult = true; + bool bIsPositionValid = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePositionValid(Node, &bResult)) && bResult; + bool bIsOrientationValid = OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodeOrientationValid(Node, &bResult)) && bResult; + + if (bIsPositionValid || bIsOrientationValid) + { + OculusXRHMD::FSettings* Settings; + OculusXRHMD::FGameFrame* CurrentFrame; + + if (IsInGameThread()) + { + Settings = OculusXRHMD->GetSettings(); + CurrentFrame = OculusXRHMD->GetNextFrameToRender(); + } + else + { + Settings = OculusXRHMD->GetSettings_RenderThread(); + CurrentFrame = OculusXRHMD->GetFrame_RenderThread(); + } + + if (Settings) + { + ovrpPoseStatef InPoseState; + OculusXRHMD::FPose OutPose; + + EOculusXRControllerPoseAlignment ControllerPoseAlignment = Settings->ControllerPoseAlignment; + switch (CVarOculusControllerPose.GetValueOnAnyThread()) + { + case 1: + ControllerPoseAlignment = EOculusXRControllerPoseAlignment::Default; + break; + case 2: + ControllerPoseAlignment = EOculusXRControllerPoseAlignment::Grip; + break; + case 3: + ControllerPoseAlignment = EOculusXRControllerPoseAlignment::Aim; + break; + default: + break; + } + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame ? CurrentFrame->FrameNumber : OVRP_CURRENT_FRAMEINDEX, Node, &InPoseState)) && + OculusXRHMD->ConvertPose_Internal(InPoseState.Pose, OutPose, Settings, WorldToMetersScale)) + { + FName FinalMotionSource = MotionSource; + if (FinalMotionSource == FName("Left") || FinalMotionSource == FName("Right")) + { + switch (ControllerPoseAlignment) + { + case EOculusXRControllerPoseAlignment::Grip: + FinalMotionSource = FName(FinalMotionSource.ToString().Append(FString("Grip"))); + break; + case EOculusXRControllerPoseAlignment::Aim: + FinalMotionSource = FName(FinalMotionSource.ToString().Append(FString("Aim"))); + break; + case EOculusXRControllerPoseAlignment::Default: + default: + break; + } + } + + // TODO: Just pass the pose info to OVRPlugin instead of doing the conversion between poses here + if (FinalMotionSource == FName("LeftGrip") || FinalMotionSource == FName("RightGrip")) + { + OutPose = OutPose * OculusXRHMD::FPose(FQuat(FVector(0, 1, 0), -FMath::DegreesToRadians(double(60))), FVector(-0.04, 0, -0.03) * WorldToMetersScale); + } + else if (FinalMotionSource == FName("LeftAim") || FinalMotionSource == FName("RightAim")) + { + OutPose = OutPose * OculusXRHMD::FPose(FQuat::Identity, FVector(0.055, 0, 0) * WorldToMetersScale); + } + + if (bIsPositionValid) + { + OutPosition = OutPose.Position; + } + + if (bIsOrientationValid) + { + OutOrientation = OutPose.Orientation.Rotator(); + } + + auto bSuccess = true; + UOculusXRInputFunctionLibrary::HandMovementFilter.Broadcast( + MotionSourceMap[FinalMotionSource].ControllerHand, + &OutPosition, + &OutOrientation, + &bSuccess); + + return bSuccess; + } + } + } + } + } + + break; + } + } + } + + auto bSuccess = false; + EControllerHand ControllerHand; + if (GetHandEnumForSourceName(MotionSource, ControllerHand)) + { + UOculusXRInputFunctionLibrary::HandMovementFilter.Broadcast( + ControllerHand, + &OutPosition, + &OutOrientation, + &bSuccess); + } + return bSuccess; +} + +bool FOculusXRInput::GetControllerOrientationAndPosition(const int32 ControllerIndex, const EControllerHand DeviceHand, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const +{ + FName MotionSource; + switch (DeviceHand) + { + case EControllerHand::Left: + MotionSource = FName("Left"); + break; + case EControllerHand::Right: + MotionSource = FName("Right"); + break; + default: + MotionSource = FName("Unknown"); + break; + } + return GetControllerOrientationAndPosition(ControllerIndex, MotionSource, OutOrientation, OutPosition, WorldToMetersScale); +} + +ETrackingStatus FOculusXRInput::GetControllerTrackingStatus(const int32 ControllerIndex, const EControllerHand DeviceHand) const +{ + ETrackingStatus TrackingStatus = ETrackingStatus::NotTracked; + + if (DeviceHand != EControllerHand::Left && DeviceHand != EControllerHand::Right) + { + return TrackingStatus; + } + + for( const FOculusControllerPair& ControllerPair : ControllerPairs ) + { + if( ControllerPair.UnrealControllerIndex == ControllerIndex ) + { + const FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[ (int32)DeviceHand ]; + if (ControllerState.bIsConnected) + { + if (ControllerState.bIsPositionTracked && ControllerState.bIsOrientationTracked) + { + TrackingStatus = ETrackingStatus::Tracked; + } + else if (ControllerState.bIsPositionValid && ControllerState.bIsOrientationValid) + { + TrackingStatus = ETrackingStatus::InertialOnly; + } + + break; + } + + const FOculusHandControllerState& HandState = ControllerPair.HandControllerStates[(int32)DeviceHand]; + if (HandState.bIsConnected) + { + if (HandState.bIsPositionTracked && HandState.bIsOrientationTracked) + { + TrackingStatus = ETrackingStatus::Tracked; + } + + break; + } + } + } + + return TrackingStatus; +} + +void FOculusXRInput::SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values) +{ + SetHapticFeedbackValues(ControllerId, Hand, Values, nullptr); +} + +void FOculusXRInput::SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values, TSharedPtr<FOculusXRHapticsDesc> HapticsDesc) +{ + for (FOculusControllerPair& ControllerPair : ControllerPairs) + { + if (ControllerPair.UnrealControllerIndex == ControllerId) + { + FOculusTouchControllerState& ControllerState = ControllerPair.TouchControllerStates[Hand]; + if (ControllerState.bIsConnected) + { + if (IOculusXRHMDModule::IsAvailable() && FOculusXRHMDModule::GetPluginWrapper().GetInitialized() && FApp::HasVRFocus()) + { + ovrpController ControllerTypes = (ovrpController)(ovrpController_Active | ovrpController_LTrackedRemote | ovrpController_RTrackedRemote); +#ifdef USE_ANDROID_INPUT + ControllerTypes = (ovrpController)(ControllerTypes | ovrpController_Touch); +#endif + ovrpControllerState5 OvrpControllerState; + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetControllerState5(ControllerTypes, &OvrpControllerState))) + { + UE_LOG(LogOcInput, Error, TEXT("GetControllerState5 failed.")); + return; + } + if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch | ovrpController_LTrackedRemote | ovrpController_RTrackedRemote)) + { + // Buffered haptics is currently only supported on Touch + FHapticFeedbackBuffer* HapticBuffer = Values.HapticBuffer; + bool bHapticBuffer = (HapticBuffer && HapticBuffer->BufferLength > 0); + if ((OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch)) && bHapticBuffer) + { + const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; + ovrpHapticsState OvrpHapticsState; + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetControllerHapticsState2(OvrpController, &OvrpHapticsState))) + { + UE_LOG(LogOcInput, Error, TEXT("ControllerHapticsState2 failed.")); + return; + } + double StartTimePCM = FPlatformTime::Seconds(); + float TimeToSend = GetMaxHapticDuration(EControllerHand(Hand)); + int WantToSend = (int)(TimeToSend * HapticBuffer->SamplingRate); + if (WantToSend == 0) + return; + WantToSend = FMath::Min(WantToSend, OvrpHapticsDesc.MaximumBufferSamplesCount); + WantToSend = FMath::Max(WantToSend, OvrpHapticsDesc.MinimumBufferSamplesCount); + + ovrpUInt32 SamplesSent = 0; + if (OvrpHapticsState.SamplesQueued < OvrpHapticsDesc.MinimumSafeSamplesQueued + WantToSend) //trying to minimize latency + { + WantToSend = (OvrpHapticsDesc.MinimumSafeSamplesQueued + WantToSend - OvrpHapticsState.SamplesQueued); + void* BufferToFree = NULL; + ovrpHapticsBuffer OvrpHapticsBuffer; + WantToSend = FMath::Min(WantToSend, HapticBuffer->BufferLength - HapticBuffer->SamplesSent); + WantToSend = FMath::Min(WantToSend, (int)(0.001f * CVarOculusPCMBatchDuration.GetValueOnAnyThread() * HapticBuffer->SamplingRate)); + TimeToSend = 1.f * WantToSend / HapticBuffer->SamplingRate; + OvrpHapticsBuffer.SamplesCount = WantToSend; + if (OvrpHapticsBuffer.SamplesCount == 0 && OvrpHapticsState.SamplesQueued == 0) + { + Values.HapticBuffer->bFinishedPlaying = HapticBuffer->bFinishedPlaying = true; + + ControllerState.bPlayingHapticEffect = false; + } + else + { + if (OvrpHapticsDesc.SampleSizeInBytes == 1) + { + uint8* Samples = (uint8*)FMemory::Malloc(OvrpHapticsBuffer.SamplesCount * sizeof(*Samples)); + for (int i = 0; i < OvrpHapticsBuffer.SamplesCount; i++) + { + Samples[i] = static_cast<uint8>(HapticBuffer->RawData[HapticBuffer->CurrentPtr + i] * HapticBuffer->ScaleFactor); + } + OvrpHapticsBuffer.Samples = BufferToFree = Samples; + } + else if (OvrpHapticsDesc.SampleSizeInBytes == 2) + { + uint16* Samples = (uint16*)FMemory::Malloc(OvrpHapticsBuffer.SamplesCount * sizeof(*Samples)); + for (int i = 0; i < OvrpHapticsBuffer.SamplesCount; i++) + { + const uint32 DataIndex = HapticBuffer->CurrentPtr + (i * 2); + const uint16* const RawData = reinterpret_cast<const uint16*>(&HapticBuffer->RawData[DataIndex]); + Samples[i] = static_cast<uint16>(*RawData * HapticBuffer->ScaleFactor); + } + OvrpHapticsBuffer.Samples = BufferToFree = Samples; + } + else if (OvrpHapticsDesc.SampleSizeInBytes == 4) + { + uint32* Samples = (uint32*)FMemory::Malloc(OvrpHapticsBuffer.SamplesCount * sizeof(*Samples)); + for (int i = 0; i < OvrpHapticsBuffer.SamplesCount; i++) + { + const uint32 DataIndex = HapticBuffer->CurrentPtr + (i * 4); + const uint32* const RawData = reinterpret_cast<const uint32*>(&HapticBuffer->RawData[DataIndex]); + Samples[i] = static_cast<uint32>(*RawData * HapticBuffer->ScaleFactor); + } + OvrpHapticsBuffer.Samples = BufferToFree = Samples; + } + else + { + UE_LOG(LogOcInput, Error, TEXT("Unsupported OvrpHapticsDesc.SampleSizeInBytes: %d."), OvrpHapticsDesc.SampleSizeInBytes); + return; + } + + ovrpHapticsPcmVibration HapticsVibration; + bool bAppend = HapticsDesc ? HapticsDesc->bAppend : false; + HapticsVibration.Append = (bAppend || HapticBuffer->SamplesSent > 0); + float* PCMBuffer = (float*)FMemory::Malloc(OvrpHapticsBuffer.SamplesCount * sizeof(*PCMBuffer)); + for (int i = 0; i < OvrpHapticsBuffer.SamplesCount; i++) + { + float Amplitude = ((uint8_t*)OvrpHapticsBuffer.Samples)[i] / 255.0f; + Amplitude = FMath::Min(1.0f, Amplitude); + Amplitude = FMath::Max(-1.0f, Amplitude); + PCMBuffer[i] = Amplitude; + } + HapticsVibration.Buffer = PCMBuffer; + HapticsVibration.BufferSize = (ovrpUInt32)OvrpHapticsBuffer.SamplesCount; + HapticsVibration.SampleRateHz = HapticBuffer->SamplingRate; + HapticsVibration.SamplesConsumed = &SamplesSent; + FOculusXRHMDModule::GetPluginWrapper().SetControllerHapticsPcm( + OvrpController, + HapticsVibration); + double EndTimePCM = FPlatformTime::Seconds(); + if (PCMBuffer) + { + FMemory::Free(PCMBuffer); + } + UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("PCMHaptics is finished: bAppend: %d, BufferSize: %d, SampleRate: %.3f, SamplesConsumed: %d, Total SamplesSent: %d, TimeSpent: %fms"), + (int)(HapticsVibration.Append), + HapticsVibration.BufferSize, + HapticsVibration.SampleRateHz, + SamplesSent, + HapticBuffer->SamplesSent + SamplesSent, + (EndTimePCM - StartTimePCM) * 1000.0); + + if (BufferToFree) + { + FMemory::Free(BufferToFree); + } + + HapticBuffer->CurrentPtr += (SamplesSent * OvrpHapticsDesc.SampleSizeInBytes); + HapticBuffer->SamplesSent += SamplesSent; + + ControllerState.bPlayingHapticEffect = true; + } + } + } + else + { + float FreqMin, FreqMax = 0.f; + GetHapticFrequencyRange(FreqMin, FreqMax); + + const float InitialFreq = (Values.Frequency > 0.0f) ? Values.Frequency : 1.0f; + const float Frequency = FMath::Lerp(FreqMin, FreqMax, FMath::Clamp(InitialFreq, 0.f, 1.f)); + + const float Amplitude = Values.Amplitude * GetHapticAmplitudeScale(); + + if (ControllerState.HapticAmplitude != Amplitude || ControllerState.HapticFrequency != Frequency) + { + ControllerState.HapticAmplitude = Amplitude; + ControllerState.HapticFrequency = Frequency; + + ovrpController OvrController = ovrpController_None; + if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_Touch)) + { + OvrController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; + } + else if (OvrpControllerState.ConnectedControllerTypes & (ovrpController_LTrackedRemote | ovrpController_RTrackedRemote)) + { + OvrController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTrackedRemote : ovrpController_RTrackedRemote; + } + + ovrpHapticsLocation Loc = (HapticsDesc ? GetOVRPHapticsLocation(HapticsDesc->Location) : ovrpHapticsLocation::ovrpHapticsLocation_Hand); + FOculusXRHMDModule::GetPluginWrapper().SetControllerLocalizedVibration(OvrController, + Loc, + Frequency, + Amplitude); + UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("LocalizedVibration is finished: Location: %d, Frequency: %f, Amplitude: %f"), (int)(Loc), Frequency, Amplitude); + + ControllerState.bPlayingHapticEffect = (Amplitude != 0.f) && (Frequency != 0.f); + } + } + } + } + } + + break; + } + } +} + +void FOculusXRInput::PlayHapticEffect( + UHapticFeedbackEffect_Base* HapticEffect, + EControllerHand Hand, + EOculusXRHandHapticsLocation Location, + bool bAppend, + float Scale, + bool bLoop) +{ + if (HapticEffect) + { + switch (Hand) + { + case EControllerHand::Left: + ActiveHapticEffect_Left.Reset(); + HapticsDesc_Left.Reset(); + ActiveHapticEffect_Left = MakeShareable(new FActiveHapticFeedbackEffect(HapticEffect, Scale, bLoop)); + HapticsDesc_Left = MakeShareable(new FOculusXRHapticsDesc(Location, bAppend)); + break; + case EControllerHand::Right: + ActiveHapticEffect_Right.Reset(); + HapticsDesc_Right.Reset(); + ActiveHapticEffect_Right = MakeShareable(new FActiveHapticFeedbackEffect(HapticEffect, Scale, bLoop)); + HapticsDesc_Right = MakeShareable(new FOculusXRHapticsDesc(Location, bAppend)); + break; + default: + UE_LOG(LogOcInput, Warning, TEXT("Invalid hand specified (%d) for haptic feedback effect %s"), (int32)Hand, *HapticEffect->GetName()); + break; + } + } +} + +int FOculusXRInput::PlayHapticEffect(EControllerHand Hand, int SamplesCount, void* Samples, int InSampleRate, bool bPCM, bool bAppend) +{ + int TimeToSend = GetMaxHapticDuration(Hand); + if (TimeToSend == 0) + return 0; + + const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; + int SampleRate = (InSampleRate > 0 ? InSampleRate : OvrpHapticsDesc.SampleRateHz); + int MaxSamplesCount = TimeToSend * SampleRate; + if (SamplesCount > MaxSamplesCount || + SamplesCount < OvrpHapticsDesc.MinimumBufferSamplesCount) + { + UE_LOG(LogOcInput, Error, TEXT("Sample count should be between %d and %d which last %d time."), + OvrpHapticsDesc.MinimumBufferSamplesCount, MaxSamplesCount, TimeToSend); + } + int WantToSend = FMath::Min(SamplesCount, MaxSamplesCount); + WantToSend = FMath::Max(WantToSend, OvrpHapticsDesc.MinimumBufferSamplesCount); + + float* BufferToSend = (float*)FMemory::Malloc(WantToSend * sizeof(*BufferToSend)); + for (int i = 0; i < WantToSend; i++) + { + float Amplitude = ((uint8_t*)Samples)[i] / 255.0f; + Amplitude = FMath::Min(1.0f, Amplitude); + Amplitude = FMath::Max(0.0f, Amplitude); + BufferToSend[i] = Amplitude; + UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("amplitude, %.3f"), Amplitude); + } + + ovrpUInt32 SamplesSent = 0; + if (bPCM) + { //PCM + ovrpHapticsPcmVibration HapticsVibration; + HapticsVibration.Buffer = BufferToSend; + HapticsVibration.BufferSize = (ovrpUInt32)WantToSend; + HapticsVibration.SampleRateHz = SampleRate; + HapticsVibration.SamplesConsumed = &SamplesSent; + FOculusXRHMDModule::GetPluginWrapper().SetControllerHapticsPcm( + OvrpController, + HapticsVibration); + UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("PCMHaptics is finished: bAppend: %d, BufferSize: %d, SampleRate: %.3f, SamplesConsumed: %d"), + (int)(HapticsVibration.Append), + HapticsVibration.BufferSize, + HapticsVibration.SampleRateHz, + SamplesSent); + } + else + { //HAE + ovrpHapticsAmplitudeEnvelopeVibration HapticsVibration; + HapticsVibration.Duration = WantToSend / SampleRate; + HapticsVibration.AmplitudeCount = WantToSend; + HapticsVibration.Amplitudes = BufferToSend; + + FOculusXRHMDModule::GetPluginWrapper().SetControllerHapticsAmplitudeEnvelope( + OvrpController, + HapticsVibration); + UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("HAEHaptics is finished: AmplitudeCount: %d, SampleRate: %d"), + HapticsVibration.AmplitudeCount, + SampleRate); + } + if (BufferToSend) + { + FMemory::Free(BufferToSend); + } + return (int)SamplesSent; +} + +void FOculusXRInput::SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location) +{ + const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; + FOculusXRHMDModule::GetPluginWrapper().SetControllerLocalizedVibration(OvrpController, GetOVRPHapticsLocation(Location), Frequency, Amplitude); + UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("LocalizedVibration is finished: Location: %d, Frequency: %f, Amplitude: %f"), (int)(Location), Frequency, Amplitude); +} + +void FOculusXRInput::ProcessHaptics(const float DeltaTime) +{ + FHapticFeedbackValues LeftHaptics, RightHaptics; + bool bLeftHapticsNeedUpdate = false; + bool bRightHapticsNeedUpdate = false; + + if (ActiveHapticEffect_Left.IsValid()) + { + const bool bPlaying = ActiveHapticEffect_Left->Update(DeltaTime, LeftHaptics); + if (!bPlaying) + { + ActiveHapticEffect_Left->bLoop ? HapticsDesc_Left->Restart() : HapticsDesc_Left.Reset(); + ActiveHapticEffect_Left->bLoop ? ActiveHapticEffect_Left->Restart() : ActiveHapticEffect_Left.Reset(); + } + + bLeftHapticsNeedUpdate = true; + } + + if (ActiveHapticEffect_Right.IsValid()) + { + const bool bPlaying = ActiveHapticEffect_Right->Update(DeltaTime, RightHaptics); + if (!bPlaying) + { + ActiveHapticEffect_Right->bLoop ? HapticsDesc_Right->Restart() : HapticsDesc_Right.Reset(); + ActiveHapticEffect_Right->bLoop ? ActiveHapticEffect_Right->Restart() : ActiveHapticEffect_Right.Reset(); + } + + bRightHapticsNeedUpdate = true; + } + + // Haptic Updates + if (bLeftHapticsNeedUpdate) + { + SetHapticFeedbackValues(0, (int32)(EControllerHand::Left), LeftHaptics, HapticsDesc_Left); + } + if (bRightHapticsNeedUpdate) + { + SetHapticFeedbackValues(0, (int32)(EControllerHand::Right), RightHaptics, HapticsDesc_Right); + } +} + +void FOculusTouchControllerState::ResampleHapticBufferData(const FHapticFeedbackBuffer& HapticBuffer, TMap<const uint8*, TSharedPtr<TArray<uint8>>>& ResampledRawDataCache) +{ + const uint8* OriginalRawData = HapticBuffer.RawData; + TSharedPtr<TArray<uint8>>* ResampledRawDataSharedPtrPtr = ResampledRawDataCache.Find(OriginalRawData); + if (ResampledRawDataSharedPtrPtr == nullptr) + { + // We need to resample and cache the resampled data. + + ResampledHapticBuffer = HapticBuffer; + + int32 SampleRate = HapticBuffer.SamplingRate; + int TargetFrequency = 320; + int TargetBufferSize = (HapticBuffer.BufferLength * TargetFrequency) / (SampleRate * 2) + 1; //2 because we're only using half of the 16bit source PCM buffer + ResampledHapticBuffer.BufferLength = TargetBufferSize; + ResampledHapticBuffer.CurrentPtr = 0; + ResampledHapticBuffer.SamplingRate = TargetFrequency; + + TSharedPtr<TArray<uint8>>& NewResampledRawDataSharedPtr = ResampledRawDataCache.Add(OriginalRawData); + NewResampledRawDataSharedPtr = MakeShared<TArray<uint8>>(); + ResampledRawDataSharedPtrPtr = &NewResampledRawDataSharedPtr; + TArray<uint8>& ResampledRawData = *NewResampledRawDataSharedPtr; + ResampledRawData.SetNum(TargetBufferSize); + + const uint8* PCMData = HapticBuffer.RawData; + + int previousTargetIndex = -1; + int currentMin = 0; + for (int i = 1; i < HapticBuffer.BufferLength; i += 2) + { + int targetIndex = i * TargetFrequency / (SampleRate * 2); + int val = PCMData[i]; + if (val & 0x80) + { + val = ~val; + } + currentMin = FMath::Min(currentMin, val); + + if (targetIndex != previousTargetIndex) + { + + ResampledRawData[targetIndex] = val * 2;// *Scale; + previousTargetIndex = targetIndex; + currentMin = 0; + } + } + + ResampledHapticBuffer.RawData = ResampledRawData.GetData(); + } + else if (ResampledHapticBuffer.RawData != (*ResampledRawDataSharedPtrPtr)->GetData()) + { + // If this a cached effect, but not the same one we played last so we need to copy the new one's buffer and reference its cached resampled data. + ResampledHapticBuffer = HapticBuffer; + ResampledHapticBuffer.RawData = (*ResampledRawDataSharedPtrPtr)->GetData(); + } +} + +void FOculusXRInput::GetHapticFrequencyRange(float& MinFrequency, float& MaxFrequency) const +{ + MinFrequency = 0.f; + MaxFrequency = 1.f; +} + +float FOculusXRInput::GetHapticAmplitudeScale() const +{ + return 1.f; +} + +uint32 FOculusXRInput::GetNumberOfTouchControllers() const +{ + uint32 RetVal = 0; + + for (FOculusControllerPair Pair : ControllerPairs) + { + RetVal += (Pair.TouchControllerStates[0].bIsConnected ? 1 : 0); + RetVal += (Pair.TouchControllerStates[1].bIsConnected ? 1 : 0); + } + + return RetVal; +} + +uint32 FOculusXRInput::GetNumberOfHandControllers() const +{ + uint32 RetVal = 0; + + for (FOculusControllerPair Pair : ControllerPairs) + { + RetVal += (Pair.HandControllerStates[0].bIsConnected ? 1 : 0); + RetVal += (Pair.HandControllerStates[1].bIsConnected ? 1 : 0); + } + + return RetVal; +} + +ovrpHapticsLocation FOculusXRInput::GetOVRPHapticsLocation(EOculusXRHandHapticsLocation Location) +{ + switch (Location) + { + case EOculusXRHandHapticsLocation::Hand: + return ovrpHapticsLocation::ovrpHapticsLocation_Hand; + case EOculusXRHandHapticsLocation::Thumb: + return ovrpHapticsLocation::ovrpHapticsLocation_Thumb; + case EOculusXRHandHapticsLocation::Index: + return ovrpHapticsLocation::ovrpHapticsLocation_Index; + default: + UE_LOG(LogOcInput, Error, TEXT("Unsupported Haptics Location: %d"), Location); + return ovrpHapticsLocation::ovrpHapticsLocation_None; + } +} + +bool FOculusXRInput::GetOvrpHapticsDesc(int Hand) +{ + if (!bPulledHapticsDesc) + { + const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; + // Buffered haptics is currently only supported on Touch + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetControllerHapticsDesc2(OvrpController, &OvrpHapticsDesc))) + { + UE_LOG(LogOcInput, Error, TEXT("ControllerHapticsDesc2 failed.")); + return false; + } + bPulledHapticsDesc = true; + if (OvrpHapticsDesc.SampleRateHz == 0) + { + UE_LOG(LogOcInput, Error, TEXT("GetControllerHapticsDesc2 returns OvrpHapticsDesc.SampleRateHz = %d"), OvrpHapticsDesc.SampleRateHz); + OvrpHapticsDesc.SampleRateHz = 2000; + return true; + } + else + { + UE_CLOG(OVR_HAP_LOGGING, LogOcInput, Log, TEXT("GetControllerHapticsDesc2 returns OvrpHapticsDesc.SampleRateHz = %d"), OvrpHapticsDesc.SampleRateHz); + } + } + return true; +} + +float FOculusXRInput::GetControllerSampleRateHz(EControllerHand Hand) +{ + float sampleRateHz = 0.f; + const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetControllerSampleRateHz(OvrpController, &sampleRateHz))) + { + UE_LOG(LogOcInput, Error, TEXT("GetControllerSampleRateHz failed.")); + } + return sampleRateHz; +} + +int FOculusXRInput::GetMaxHapticDuration(EControllerHand Hand) +{ + const ovrpController OvrpController = (EControllerHand(Hand) == EControllerHand::Left) ? ovrpController_LTouch : ovrpController_RTouch; + if (!GetOvrpHapticsDesc((int32)Hand)) + return 0; + + return OvrpHapticsDesc.MaximumBufferSamplesCount / OvrpHapticsDesc.SampleRateHz; +} +} // namespace OculusXRInput + +#undef LOCTEXT_NAMESPACE +#endif // OCULUS_INPUT_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInput.h b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInput.h new file mode 100644 index 0000000000000000000000000000000000000000..3f59070ea393e965ca0b7b1102d6db724de5c554 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInput.h @@ -0,0 +1,146 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "IOculusXRInputModule.h" + +#if OCULUS_INPUT_SUPPORTED_PLATFORMS +#include "OculusXRHMDModule.h" +#include "GenericPlatform/IInputInterface.h" +#include "XRMotionControllerBase.h" +#include "IHapticDevice.h" +#include "OculusXRInputState.h" + +#if PLATFORM_SUPPORTS_PRAGMA_PACK + #pragma pack (push,8) +#endif + +#include "OculusXRPluginWrapper.h" + +#if PLATFORM_SUPPORTS_PRAGMA_PACK + #pragma pack (pop) +#endif + +DEFINE_LOG_CATEGORY_STATIC(LogOcInput, Log, All); + + +class UHapticFeedbackEffect_Base; +struct FActiveHapticFeedbackEffect; +struct FOculusXRHapticsDesc; + +namespace OculusXRInput +{ + +//------------------------------------------------------------------------------------------------- +// FOculusXRInput +//------------------------------------------------------------------------------------------------- + +class FOculusXRInput : public IInputDevice, public FXRMotionControllerBase, public IHapticDevice +{ + friend class FOculusHandTracking; + +public: + + /** Constructor that takes an initial message handler that will receive motion controller events */ + FOculusXRInput( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ); + + /** Clean everything up */ + virtual ~FOculusXRInput(); + + static void PreInit(); + + /** Loads any settings from the config folder that we need */ + static void LoadConfig(); + + // IInputDevice overrides + virtual void Tick( float DeltaTime ) override; + virtual void SendControllerEvents() override; + virtual void SetMessageHandler( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) override; + virtual bool Exec( UWorld* InWorld, const TCHAR* Cmd, FOutputDevice& Ar ) override; + virtual void SetChannelValue( int32 ControllerId, FForceFeedbackChannelType ChannelType, float Value ) override; + virtual void SetChannelValues( int32 ControllerId, const FForceFeedbackValues& Values ) override; + virtual bool SupportsForceFeedback(int32 ControllerId) override; + + // IMotionController overrides + virtual FName GetMotionControllerDeviceTypeName() const override; + virtual bool GetControllerOrientationAndPosition(const int32 ControllerIndex, const FName MotionSource, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const override; + virtual bool GetControllerOrientationAndPosition(const int32 ControllerIndex, const EControllerHand DeviceHand, FRotator& OutOrientation, FVector& OutPosition, float WorldToMetersScale) const override; + virtual ETrackingStatus GetControllerTrackingStatus(const int32 ControllerIndex, const EControllerHand DeviceHand) const override; + + // IHapticDevice overrides + IHapticDevice* GetHapticDevice() override { return (IHapticDevice*)this; } + virtual void SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values) override; + + void PlayHapticEffect( + UHapticFeedbackEffect_Base* HapticEffect, + EControllerHand Hand, + EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand, + bool bAppend = false, + float Scale = 1.f, + bool bLoop = false); + int PlayHapticEffect(EControllerHand Hand, int SamplesCount, void* Samples, int SampleRate = -1, bool bPCM = false, bool bAppend = false); + void SetHapticsByValue(float Frequency, float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand); + + virtual void GetHapticFrequencyRange(float& MinFrequency, float& MaxFrequency) const override; + virtual float GetHapticAmplitudeScale() const override; + + uint32 GetNumberOfTouchControllers() const; + uint32 GetNumberOfHandControllers() const; + + float GetControllerSampleRateHz(EControllerHand Hand); + int GetMaxHapticDuration(EControllerHand Hand); +private: + + /** Applies force feedback settings to the controller */ + void UpdateForceFeedback( const FOculusControllerPair& ControllerPair, const EControllerHand Hand ); + + bool OnControllerButtonPressed( const FOculusButtonState& ButtonState, int32 ControllerId, bool IsRepeat ); + bool OnControllerButtonReleased( const FOculusButtonState& ButtonState, int32 ControllerId, bool IsRepeat ); + + void SetHapticFeedbackValues(int32 ControllerId, int32 Hand, const FHapticFeedbackValues& Values, TSharedPtr<FOculusXRHapticsDesc> HapticsDesc); + ovrpHapticsLocation GetOVRPHapticsLocation(EOculusXRHandHapticsLocation InLocation); + + void ProcessHaptics(const float DeltaTime); + bool GetOvrpHapticsDesc(int Hand); +private: + + void* OVRPluginHandle; + + /** The recipient of motion controller input events */ + TSharedPtr< FGenericApplicationMessageHandler > MessageHandler; + + /** List of the connected pairs of controllers, with state for each controller device */ + TArray< FOculusControllerPair > ControllerPairs; + + FOculusRemoteControllerState Remote; + + ovrpHapticsDesc OvrpHapticsDesc; + + int LocalTrackingSpaceRecenterCount; + + // Maintain a cache of resampled raw data so we don't resample it on every play. This is a map of OriginalRawData pointers, used only as a key, to ResampledRawData buffers. + // The values are pointers because the map could be reallocated and we cache raw pointers to the uint8 array data elsewhere. + TMap<const uint8*, TSharedPtr<TArray<uint8>>> ResampledRawDataCache; + + TSharedPtr<FActiveHapticFeedbackEffect> ActiveHapticEffect_Left; + TSharedPtr<FActiveHapticFeedbackEffect> ActiveHapticEffect_Right; + TSharedPtr<FOculusXRHapticsDesc> HapticsDesc_Left; + TSharedPtr<FOculusXRHapticsDesc> HapticsDesc_Right; + double StartTime = 0.0; + + /** Threshold for treating trigger pulls as button presses, from 0.0 to 1.0 */ + static float TriggerThreshold; + + /** Are Remote keys mapped to gamepad or not. */ + static bool bRemoteKeysMappedToGamepad; + + /** Repeat key delays, loaded from config */ + static float InitialButtonRepeatDelay; + static float ButtonRepeatDelay; + + static bool bPulledHapticsDesc; +}; + + +} // namespace OculusXRInput + +#endif //OCULUS_INPUT_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputFunctionLibrary.cpp b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputFunctionLibrary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..25b289c10b718da5f1551bc7a6b14b8713733599 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputFunctionLibrary.cpp @@ -0,0 +1,165 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRInputFunctionLibrary.h" +#include "OculusXRHandTracking.h" +#include "OculusXRControllerTracking.h" +#include "Logging/MessageLog.h" +#include "Haptics/HapticFeedbackEffect_Buffer.h" +#include "Haptics/HapticFeedbackEffect_Curve.h" +#include "Haptics/HapticFeedbackEffect_SoundWave.h" + +//------------------------------------------------------------------------------------------------- +// UOculusHandTrackingFunctionLibrary +//------------------------------------------------------------------------------------------------- +UOculusXRInputFunctionLibrary::UOculusXRInputFunctionLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +UOculusXRInputFunctionLibrary::FHandMovementFilterDelegate UOculusXRInputFunctionLibrary::HandMovementFilter; + +EOculusXRFinger UOculusXRInputFunctionLibrary::ConvertBoneToFinger(const EOculusXRBone Bone) +{ + switch (Bone) + { + case EOculusXRBone::Index_1: + case EOculusXRBone::Index_2: + case EOculusXRBone::Index_3: + case EOculusXRBone::Index_Tip: + return EOculusXRFinger::Index; + case EOculusXRBone::Middle_1: + case EOculusXRBone::Middle_2: + case EOculusXRBone::Middle_3: + case EOculusXRBone::Middle_Tip: + return EOculusXRFinger::Middle; + case EOculusXRBone::Pinky_0: + case EOculusXRBone::Pinky_1: + case EOculusXRBone::Pinky_2: + case EOculusXRBone::Pinky_3: + case EOculusXRBone::Pinky_Tip: + return EOculusXRFinger::Pinky; + case EOculusXRBone::Ring_1: + case EOculusXRBone::Ring_2: + case EOculusXRBone::Ring_3: + case EOculusXRBone::Ring_Tip: + return EOculusXRFinger::Ring; + case EOculusXRBone::Thumb_0: + case EOculusXRBone::Thumb_1: + case EOculusXRBone::Thumb_2: + case EOculusXRBone::Thumb_3: + case EOculusXRBone::Thumb_Tip: + return EOculusXRFinger::Thumb; + default: + return EOculusXRFinger::Invalid; + } +} + +EOculusXRTrackingConfidence UOculusXRInputFunctionLibrary::GetFingerTrackingConfidence(const EOculusXRHandType DeviceHand, const EOculusXRFinger Finger, const int32 ControllerIndex) +{ + return OculusXRInput::FOculusHandTracking::GetFingerTrackingConfidence(ControllerIndex, DeviceHand, (OculusXRInput::EOculusHandAxes)(uint8)Finger); +} + +bool UOculusXRInputFunctionLibrary::GetHandSkeletalMesh(USkeletalMesh* HandSkeletalMesh, EOculusXRHandType SkeletonType, EOculusXRHandType MeshType, float WorldToMeters) +{ + return OculusXRInput::FOculusHandTracking::GetHandSkeletalMesh(HandSkeletalMesh, SkeletonType, MeshType, WorldToMeters); +} + +TArray<FOculusXRCapsuleCollider> UOculusXRInputFunctionLibrary::InitializeHandPhysics(EOculusXRHandType SkeletonType, USkinnedMeshComponent* HandComponent, const float WorldToMeters) +{ + return OculusXRInput::FOculusHandTracking::InitializeHandPhysics(SkeletonType, HandComponent, WorldToMeters); +} + +FQuat UOculusXRInputFunctionLibrary::GetBoneRotation(const EOculusXRHandType DeviceHand, const EOculusXRBone BoneId, const int32 ControllerIndex) +{ + return OculusXRInput::FOculusHandTracking::GetBoneRotation(ControllerIndex, DeviceHand, BoneId); +} + +EOculusXRTrackingConfidence UOculusXRInputFunctionLibrary::GetTrackingConfidence(const EOculusXRHandType DeviceHand, const int32 ControllerIndex) +{ + return OculusXRInput::FOculusHandTracking::GetTrackingConfidence(ControllerIndex, DeviceHand); +} + +FTransform UOculusXRInputFunctionLibrary::GetPointerPose(const EOculusXRHandType DeviceHand, const int32 ControllerIndex) +{ + return OculusXRInput::FOculusHandTracking::GetPointerPose(ControllerIndex, DeviceHand); +} + +bool UOculusXRInputFunctionLibrary::IsPointerPoseValid(const EOculusXRHandType DeviceHand, const int32 ControllerIndex) +{ + return OculusXRInput::FOculusHandTracking::IsPointerPoseValid(ControllerIndex, DeviceHand); +} + +float UOculusXRInputFunctionLibrary::GetHandScale(const EOculusXRHandType DeviceHand, const int32 ControllerIndex) +{ + return OculusXRInput::FOculusHandTracking::GetHandScale(ControllerIndex, DeviceHand); +} + +EOculusXRHandType UOculusXRInputFunctionLibrary::GetDominantHand(const int32 ControllerIndex) +{ + EOculusXRHandType DominantHand = EOculusXRHandType::None; + if (OculusXRInput::FOculusHandTracking::IsHandDominant(ControllerIndex, EOculusXRHandType::HandLeft)) + { + DominantHand = EOculusXRHandType::HandLeft; + } + else if (OculusXRInput::FOculusHandTracking::IsHandDominant(ControllerIndex, EOculusXRHandType::HandRight)) + { + DominantHand = EOculusXRHandType::HandRight; + } + return DominantHand; +} + +bool UOculusXRInputFunctionLibrary::IsHandTrackingEnabled() +{ + return OculusXRInput::FOculusHandTracking::IsHandTrackingEnabled(); +} + +bool UOculusXRInputFunctionLibrary::IsHandPositionValid(const EOculusXRHandType DeviceHand, const int32 ControllerIndex) +{ + return OculusXRInput::FOculusHandTracking::IsHandPositionValid(ControllerIndex, DeviceHand); +} + +FString UOculusXRInputFunctionLibrary::GetBoneName(EOculusXRBone BoneId) +{ + uint32 ovrBoneId = OculusXRInput::FOculusHandTracking::ToOvrBone(BoneId); + return OculusXRInput::FOculusHandTracking::GetBoneName(ovrBoneId); +} + +void UOculusXRInputFunctionLibrary::PlayCurveHapticEffect(class UHapticFeedbackEffect_Curve* HapticEffect, EControllerHand Hand, EOculusXRHandHapticsLocation Location, float Scale, bool bLoop) +{ + OculusXRInput::FOculusXRControllerTracking::PlayHapticEffect(HapticEffect, Hand, Location, false, Scale, bLoop); +} + +void UOculusXRInputFunctionLibrary::PlayBufferHapticEffect(class UHapticFeedbackEffect_Buffer* HapticEffect, EControllerHand Hand, EOculusXRHandHapticsLocation Location, float Scale, bool bLoop) +{ + OculusXRInput::FOculusXRControllerTracking::PlayHapticEffect(HapticEffect, Hand, Location, false, Scale, bLoop); +} + +void UOculusXRInputFunctionLibrary::PlayAmplitudeEnvelopeHapticEffect(class UHapticFeedbackEffect_Buffer* HapticEffect, EControllerHand Hand) +{ + OculusXRInput::FOculusXRControllerTracking::PlayHapticEffect(Hand, HapticEffect->Amplitudes, HapticEffect->SampleRate, false, false); +} + +void UOculusXRInputFunctionLibrary::PlaySoundWaveHapticEffect(class UHapticFeedbackEffect_SoundWave* HapticEffect, EControllerHand Hand, bool bAppend, float Scale, bool bLoop) +{ + OculusXRInput::FOculusXRControllerTracking::PlayHapticEffect(HapticEffect, Hand, EOculusXRHandHapticsLocation::Hand, bAppend, Scale, bLoop); +} + +void UOculusXRInputFunctionLibrary::StopHapticEffect(EControllerHand Hand, EOculusXRHandHapticsLocation Location) +{ + OculusXRInput::FOculusXRControllerTracking::StopHapticEffect(Hand, Location); +} + +void UOculusXRInputFunctionLibrary::SetHapticsByValue(const float Frequency, const float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location) +{ + OculusXRInput::FOculusXRControllerTracking::SetHapticsByValue(Frequency, Amplitude, Hand, Location); +} + +float UOculusXRInputFunctionLibrary::GetControllerSampleRateHz(EControllerHand Hand) +{ + return OculusXRInput::FOculusXRControllerTracking::GetControllerSampleRateHz(Hand); +} + +int UOculusXRInputFunctionLibrary::GetMaxHapticDuration(EControllerHand Hand) +{ + return OculusXRInput::FOculusXRControllerTracking::GetMaxHapticDuration(Hand); +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputModule.cpp b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1d9090adeb02656f1ae4c3ba098462c29015d702 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputModule.cpp @@ -0,0 +1,74 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRInputModule.h" + +#if OCULUS_INPUT_SUPPORTED_PLATFORMS +#include "OculusXRInput.h" +#include "OculusXRHMDModule.h" + +#define LOCTEXT_NAMESPACE "OculusXRInput" + + +//------------------------------------------------------------------------------------------------- +// FOculusXRInputModule +//------------------------------------------------------------------------------------------------- + +void FOculusXRInputModule::StartupModule() +{ + IInputDeviceModule::StartupModule(); + OculusXRInput::FOculusXRInput::PreInit(); +} + + +TSharedPtr< class IInputDevice > FOculusXRInputModule::CreateInputDevice( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) +{ + if (IOculusXRHMDModule::IsAvailable()) + { + if (FOculusXRHMDModule::Get().PreInit()) + { + TSharedPtr< OculusXRInput::FOculusXRInput > InputDevice(new OculusXRInput::FOculusXRInput(InMessageHandler)); + OculusXRInputDevice = InputDevice; + return InputDevice; + } + // else, they may just not have a oculus headset plugged in (which we have to account for - no need for a warning) + } + else + { + UE_LOG(LogOcInput, Warning, TEXT("OculusXRInput plugin enabled, but OculusXRHMD plugin is not available.")); + } + return nullptr; +} + +uint32 FOculusXRInputModule::GetNumberOfTouchControllers() const +{ + if (OculusXRInputDevice.IsValid()) + { + return OculusXRInputDevice.Pin()->GetNumberOfTouchControllers(); + } + return 0; +} + +uint32 FOculusXRInputModule::GetNumberOfHandControllers() const +{ + if (OculusXRInputDevice.IsValid()) + { + return OculusXRInputDevice.Pin()->GetNumberOfHandControllers(); + } + return 0; +} + +TSharedPtr<IInputDevice> FOculusXRInputModule::GetInputDevice() const +{ + if (OculusXRInputDevice.IsValid()) + { + return OculusXRInputDevice.Pin(); + } + return NULL; +} + + +#endif // OCULUS_INPUT_SUPPORTED_PLATFORMS + +IMPLEMENT_MODULE( FOculusXRInputModule, OculusXRInput ) + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputModule.h b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputModule.h new file mode 100644 index 0000000000000000000000000000000000000000..e58019604bb4fc02b66d9177a3bb9152ea073413 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputModule.h @@ -0,0 +1,54 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "IOculusXRInputModule.h" +#include "IInputDevice.h" +#include "Templates/SharedPointer.h" + +#define LOCTEXT_NAMESPACE "OculusXRInput" + + +//------------------------------------------------------------------------------------------------- +// FOculusXRInputModule +//------------------------------------------------------------------------------------------------- + +#if OCULUS_INPUT_SUPPORTED_PLATFORMS + +namespace OculusXRInput +{ + class FOculusXRInput; +} + +class FOculusXRInputModule : public IOculusXRInputModule +{ + TWeakPtr<OculusXRInput::FOculusXRInput> OculusXRInputDevice; + + // IInputDeviceModule overrides + virtual void StartupModule() override; + virtual TSharedPtr< class IInputDevice > CreateInputDevice( const TSharedRef< FGenericApplicationMessageHandler >& InMessageHandler ) override; + + // IOculusXRInputModule overrides + virtual uint32 GetNumberOfTouchControllers() const override; + virtual uint32 GetNumberOfHandControllers() const override; + virtual TSharedPtr<IInputDevice> GetInputDevice() const override; +}; + +#else // OCULUS_INPUT_SUPPORTED_PLATFORMS + +class FOculusXRInputModule : public FDefaultModuleImpl +{ + virtual uint32 GetNumberOfTouchControllers() const + { + return 0; + }; + + virtual uint32 GetNumberOfHandControllers() const + { + return 0; + }; +}; + +#endif // OCULUS_INPUT_SUPPORTED_PLATFORMS + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputState.h b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputState.h new file mode 100644 index 0000000000000000000000000000000000000000..5319e93f455194a3d4a34d525140ff9b309f3913 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Private/OculusXRInputState.h @@ -0,0 +1,561 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "IOculusXRInputModule.h" + +#if OCULUS_INPUT_SUPPORTED_PLATFORMS +#include "IMotionController.h" +#include "InputCoreTypes.h" +#include "OculusXRInputFunctionLibrary.h" +#include "GenericPlatform/GenericApplicationMessageHandler.h" + +namespace OculusXRInput +{ + + +//------------------------------------------------------------------------------------------------- +// Button names +//------------------------------------------------------------------------------------------------- + +enum class EOculusTouchControllerButton +{ + // NOTE: The Trigger and Grip digital buttons are synthetic. Oculus hardware doesn't support a digital press for these + Trigger, + Grip, + + XA, + YB, + Thumbstick, + + Thumbstick_Up, + Thumbstick_Down, + Thumbstick_Left, + Thumbstick_Right, + + Menu, + + Thumbstick_Touch, + Trigger_Touch, + XA_Touch, + YB_Touch, + + /** Total number of controller buttons */ + TotalButtonCount +}; + +enum class EOculusRemoteControllerButton +{ + DPad_Up, + DPad_Down, + DPad_Left, + DPad_Right, + + Enter, + Back, + + VolumeUp, + VolumeDown, + Home, + + /** Total number of controller buttons */ + TotalButtonCount +}; + +enum class EOculusTouchCapacitiveAxes +{ + Thumbstick, + Trigger, + XA, + YB, + IndexPointing, + ThumbUp, + ThumbRest, + + /** Total number of capacitive axes */ + TotalAxisCount +}; + +enum class EOculusHandButton +{ + Thumb, + Index, + Middle, + Ring, + Pinky, + System, + Menu, + TotalButtonCount +}; + +enum class EOculusHandAxes +{ + Thumb, + Index, + Middle, + Ring, + Pinky, + TotalAxisCount +}; + +//------------------------------------------------------------------------------------------------- +// FOculusKey +//------------------------------------------------------------------------------------------------- + +struct FOculusKey +{ + static const FKey OculusTouch_Left_Thumbstick; + static const FKey OculusTouch_Left_Trigger; + static const FKey OculusTouch_Left_FaceButton1; // X or A + static const FKey OculusTouch_Left_FaceButton2; // Y or B + static const FKey OculusTouch_Left_IndexPointing; + static const FKey OculusTouch_Left_ThumbUp; + static const FKey OculusTouch_Left_ThumbRest; + static const FKey OculusTouch_Left_ThumbRest_Force; + static const FKey OculusTouch_Left_Stylus_Force; + static const FKey OculusTouch_Left_IndexTrigger_Curl; + static const FKey OculusTouch_Left_IndexTrigger_Slide; + + static const FKey OculusTouch_Right_Thumbstick; + static const FKey OculusTouch_Right_Trigger; + static const FKey OculusTouch_Right_FaceButton1; // X or A + static const FKey OculusTouch_Right_FaceButton2; // Y or B + static const FKey OculusTouch_Right_IndexPointing; + static const FKey OculusTouch_Right_ThumbUp; + static const FKey OculusTouch_Right_ThumbRest; + static const FKey OculusTouch_Right_ThumbRest_Force; + static const FKey OculusTouch_Right_Stylus_Force; + static const FKey OculusTouch_Right_IndexTrigger_Curl; + static const FKey OculusTouch_Right_IndexTrigger_Slide; + + static const FKey OculusRemote_DPad_Up; + static const FKey OculusRemote_DPad_Down; + static const FKey OculusRemote_DPad_Left; + static const FKey OculusRemote_DPad_Right; + + static const FKey OculusRemote_Enter; + static const FKey OculusRemote_Back; + + static const FKey OculusRemote_VolumeUp; + static const FKey OculusRemote_VolumeDown; + static const FKey OculusRemote_Home; + + static const FKey OculusHand_Left_ThumbPinch; + static const FKey OculusHand_Left_IndexPinch; + static const FKey OculusHand_Left_MiddlePinch; + static const FKey OculusHand_Left_RingPinch; + static const FKey OculusHand_Left_PinkyPinch; + + static const FKey OculusHand_Right_ThumbPinch; + static const FKey OculusHand_Right_IndexPinch; + static const FKey OculusHand_Right_MiddlePinch; + static const FKey OculusHand_Right_RingPinch; + static const FKey OculusHand_Right_PinkyPinch; + + static const FKey OculusHand_Left_SystemGesture; + static const FKey OculusHand_Right_SystemGesture; + + static const FKey OculusHand_Left_ThumbPinchStrength; + static const FKey OculusHand_Left_IndexPinchStrength; + static const FKey OculusHand_Left_MiddlePinchStrength; + static const FKey OculusHand_Left_RingPinchStrength; + static const FKey OculusHand_Left_PinkyPinchStrength; + + static const FKey OculusHand_Right_ThumbPinchStrength; + static const FKey OculusHand_Right_IndexPinchStrength; + static const FKey OculusHand_Right_MiddlePinchStrength; + static const FKey OculusHand_Right_RingPinchStrength; + static const FKey OculusHand_Right_PinkyPinchStrength; +}; + + +//------------------------------------------------------------------------------------------------- +// FOculusKeyNames +//------------------------------------------------------------------------------------------------- + +struct FOculusKeyNames +{ + typedef FName Type; + + static const FName OculusTouch_Left_Thumbstick; + static const FName OculusTouch_Left_Trigger; + static const FName OculusTouch_Left_FaceButton1; // X or A + static const FName OculusTouch_Left_FaceButton2; // Y or B + static const FName OculusTouch_Left_IndexPointing; + static const FName OculusTouch_Left_ThumbUp; + static const FName OculusTouch_Left_ThumbRest; + + static const FName OculusTouch_Right_Thumbstick; + static const FName OculusTouch_Right_Trigger; + static const FName OculusTouch_Right_FaceButton1; // X or A + static const FName OculusTouch_Right_FaceButton2; // Y or B + static const FName OculusTouch_Right_IndexPointing; + static const FName OculusTouch_Right_ThumbUp; + static const FName OculusTouch_Right_ThumbRest; + + static const FName OculusRemote_DPad_Up; + static const FName OculusRemote_DPad_Down; + static const FName OculusRemote_DPad_Left; + static const FName OculusRemote_DPad_Right; + + static const FName OculusRemote_Enter; + static const FName OculusRemote_Back; + + static const FName OculusRemote_VolumeUp; + static const FName OculusRemote_VolumeDown; + static const FName OculusRemote_Home; + + static const FName OculusHand_Left_ThumbPinch; + static const FName OculusHand_Left_IndexPinch; + static const FName OculusHand_Left_MiddlePinch; + static const FName OculusHand_Left_RingPinch; + static const FName OculusHand_Left_PinkyPinch; + + static const FName OculusHand_Right_ThumbPinch; + static const FName OculusHand_Right_IndexPinch; + static const FName OculusHand_Right_MiddlePinch; + static const FName OculusHand_Right_RingPinch; + static const FName OculusHand_Right_PinkyPinch; + + static const FName OculusHand_Left_SystemGesture; + static const FName OculusHand_Right_SystemGesture; + + static const FName OculusHand_Left_ThumbPinchStrength; + static const FName OculusHand_Left_IndexPinchStrength; + static const FName OculusHand_Left_MiddlePinchStrength; + static const FName OculusHand_Left_RingPinchStrength; + static const FName OculusHand_Left_PinkyPinchStrength; + + static const FName OculusHand_Right_ThumbPinchStrength; + static const FName OculusHand_Right_IndexPinchStrength; + static const FName OculusHand_Right_MiddlePinchStrength; + static const FName OculusHand_Right_RingPinchStrength; + static const FName OculusHand_Right_PinkyPinchStrength; +}; + + +//------------------------------------------------------------------------------------------------- +// FOculusButtonState - Digital button state +//------------------------------------------------------------------------------------------------- + +struct FOculusButtonState +{ + /** The Unreal button this maps to. Different depending on whether this is the Left or Right hand controller */ + FName Key; + + /** The Unreal button this maps to. Different depending on whether this is the Left or Right hand controller */ + FName EmulatedKey; + + /** Whether we're pressed or not. While pressed, we will generate repeat presses on a timer */ + bool bIsPressed; + + /** Next time a repeat event should be generated for each button */ + double NextRepeatTime; + + + /** Default constructor that just sets sensible defaults */ + FOculusButtonState() + : Key( NAME_None ), + EmulatedKey( NAME_None ), + bIsPressed( false ), + NextRepeatTime( 0.0 ) + { + } +}; + + +//------------------------------------------------------------------------------------------------- +// FOculusTouchCapacitiveState - Capacitive Axis State +//------------------------------------------------------------------------------------------------- + +struct FOculusAxisState +{ + /** The axis that this button state maps to */ + FName Axis; + + /** How close the finger is to this button, from 0.f to 1.f */ + float State; + + FOculusAxisState() + : Axis(NAME_None) + , State(0.f) + { + } +}; + +struct FOculusControllerState +{ + /** True if the device is connected, otherwise false */ + bool bIsConnected; + + /** True if position is being tracked, otherwise false */ + bool bIsPositionTracked; + + /** True if position is valid (tracked or estimated), otherwise false */ + bool bIsPositionValid; + + /** True if orientation is being tracked, otherwise false */ + bool bIsOrientationTracked; + + /** True if orientation is valid (tracked or estimated), otherwise false */ + bool bIsOrientationValid; + + FOculusControllerState() + : bIsConnected(false), + bIsPositionTracked(false), + bIsPositionValid(false), + bIsOrientationTracked(false), + bIsOrientationValid(false) + { + } +}; + +//------------------------------------------------------------------------------------------------- +// FOculusTouchControllerState - Input state for an Oculus motion controller +//------------------------------------------------------------------------------------------------- + +struct FOculusTouchControllerState : FOculusControllerState +{ + /** Analog trigger */ + float TriggerAxis; + + /** Grip trigger */ + float GripAxis; + + /** Thumbstick */ + FVector2D ThumbstickAxes; + + /** Thumbstick */ + FVector2D TouchpadAxes; + + /** Button states */ + FOculusButtonState Buttons[ (int32)EOculusTouchControllerButton::TotalButtonCount ]; + + /** Capacitive Touch axes */ + FOculusAxisState CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::TotalAxisCount]; + + /** Thumb Rest Force **/ + float ThumbRestForce; + + /** Stylus tip**/ + float StylusForce; + + /** Index trigger Curl**/ + float IndexTriggerCurl; + + /** Index trigger Slide**/ + float IndexTriggerSlide; + + /** Whether or not we're playing a haptic effect. If true, force feedback calls will be early-outed in favor of the haptic effect */ + bool bPlayingHapticEffect; + + /** Haptic frequency (zero to disable) */ + float HapticFrequency; + + /** Haptic amplitude (zero to disable) */ + float HapticAmplitude; + + /** Force feedback haptic frequency (zero to disable) */ + float ForceFeedbackHapticFrequency; + + /** Force feedback haptic amplitude (zero to disable) */ + float ForceFeedbackHapticAmplitude; + + /** Number of times that controller was recentered (for mobile controllers) */ + int RecenterCount; + +public: + FHapticFeedbackBuffer ResampledHapticBuffer; + void ResampleHapticBufferData(const FHapticFeedbackBuffer& HapticBuffer, TMap<const uint8*, TSharedPtr<TArray<uint8>>>& ResampledRawDataCache); + + /** Explicit constructor sets up sensible defaults */ + FOculusTouchControllerState( const EControllerHand Hand ) + : TriggerAxis( 0.0f ), + GripAxis( 0.0f ), + ThumbstickAxes( FVector2D::ZeroVector ), + bPlayingHapticEffect( false ), + HapticFrequency( 0.0f ), + HapticAmplitude( 0.0f ), + ForceFeedbackHapticFrequency(0.0f), + ForceFeedbackHapticAmplitude(0.0f), + RecenterCount(0) + { + for( FOculusButtonState& Button : Buttons ) + { + Button.bIsPressed = false; + Button.NextRepeatTime = 0.0; + } + + Buttons[ (int32)EOculusTouchControllerButton::Trigger ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Trigger_Click.GetFName() : EKeys::OculusTouch_Right_Trigger_Click.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::Grip ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Grip_Click.GetFName() : EKeys::OculusTouch_Right_Grip_Click.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::Thumbstick ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Click.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Click.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::XA ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_X_Click.GetFName() : EKeys::OculusTouch_Right_A_Click.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::YB ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Y_Click.GetFName() : EKeys::OculusTouch_Right_B_Click.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::Thumbstick_Up ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Up.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Up.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::Thumbstick_Down ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Down.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Down.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::Thumbstick_Left ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Left.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Left.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::Thumbstick_Right ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Right.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Right.GetFName(); + + Buttons[(int32)EOculusTouchControllerButton::Menu].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Menu_Click.GetFName() : EKeys::OculusTouch_Right_System_Click.GetFName(); + + Buttons[ (int32)EOculusTouchControllerButton::Thumbstick_Touch ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Thumbstick_Touch.GetFName() : EKeys::OculusTouch_Right_Thumbstick_Touch.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::Trigger_Touch ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Trigger_Touch.GetFName() : EKeys::OculusTouch_Right_Trigger_Touch.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::XA_Touch ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_X_Touch.GetFName() : EKeys::OculusTouch_Right_A_Touch.GetFName(); + Buttons[ (int32)EOculusTouchControllerButton::YB_Touch ].Key = (Hand == EControllerHand::Left) ? EKeys::OculusTouch_Left_Y_Touch.GetFName() : EKeys::OculusTouch_Right_B_Touch.GetFName(); + + CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::Thumbstick].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_Thumbstick : FOculusKeyNames::OculusTouch_Right_Thumbstick; + CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::Trigger].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_Trigger : FOculusKeyNames::OculusTouch_Right_Trigger; + CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::XA].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_FaceButton1 : FOculusKeyNames::OculusTouch_Right_FaceButton1; + CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::YB].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_FaceButton2 : FOculusKeyNames::OculusTouch_Right_FaceButton2; + CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::IndexPointing].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_IndexPointing : FOculusKeyNames::OculusTouch_Right_IndexPointing; + CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::ThumbUp].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_ThumbUp : FOculusKeyNames::OculusTouch_Right_ThumbUp; + CapacitiveAxes[(int32)EOculusTouchCapacitiveAxes::ThumbRest].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusTouch_Left_ThumbRest : FOculusKeyNames::OculusTouch_Right_ThumbRest; + } + + /** Default constructor does nothing. Don't use it. This only exists because we cannot initialize an array of objects with no default constructor on non-C++ 11 compliant compilers (VS 2013) */ + FOculusTouchControllerState() + { + } +}; + +//------------------------------------------------------------------------------------------------- +// FOculusHandControllerState - Input state for an Oculus Hands +//------------------------------------------------------------------------------------------------- + +struct FOculusHandControllerState : FOculusControllerState +{ + /** True if the pointer pose for hands is valid */ + bool bIsPointerPoseValid; + + /** True if the current hand is the dominant hand */ + bool bIsDominantHand; + + /** Scale of the hand */ + float HandScale; + + /** Pose of the pointer */ + FTransform PointerPose; + + /** Tracking confidence of hand tracking */ + EOculusXRTrackingConfidence TrackingConfidence; + + /** Finger Pinch States **/ + FOculusButtonState HandButtons[(int32)EOculusHandButton::TotalButtonCount]; + + /** Finger Pinch Strength States **/ + FOculusAxisState HandAxes[(int32)EOculusHandAxes::TotalAxisCount]; + + /** Finger Confidences **/ + EOculusXRTrackingConfidence FingerConfidences[(int32)EOculusHandAxes::TotalAxisCount] = {}; + + FQuat BoneRotations[(int32)EOculusXRBone::Bone_Max]; + + FOculusHandControllerState(const EControllerHand Hand) + { + TrackingConfidence = EOculusXRTrackingConfidence::Low; + bIsPointerPoseValid = false; + bIsDominantHand = false; + HandScale = 0.0f; + PointerPose = FTransform::Identity; + + for (FOculusButtonState& Button : HandButtons) + { + Button.bIsPressed = false; + Button.NextRepeatTime = 0.0; + } + + for (int32 i = 0; i < (int32)EOculusXRBone::Bone_Max; i++) + { + BoneRotations[i] = FQuat::Identity; + } + + HandButtons[(int32)EOculusHandButton::Thumb].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_ThumbPinch : FOculusKeyNames::OculusHand_Right_ThumbPinch; + HandButtons[(int32)EOculusHandButton::Index].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_IndexPinch : FOculusKeyNames::OculusHand_Right_IndexPinch; + HandButtons[(int32)EOculusHandButton::Middle].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_MiddlePinch : FOculusKeyNames::OculusHand_Right_MiddlePinch; + HandButtons[(int32)EOculusHandButton::Ring].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_RingPinch : FOculusKeyNames::OculusHand_Right_RingPinch; + HandButtons[(int32)EOculusHandButton::Pinky].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_PinkyPinch : FOculusKeyNames::OculusHand_Right_PinkyPinch; + HandButtons[(int32)EOculusHandButton::System].Key = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_SystemGesture : FOculusKeyNames::OculusHand_Right_SystemGesture; + HandButtons[(int32)EOculusHandButton::Menu].Key = (Hand == EControllerHand::Left) ? FGamepadKeyNames::SpecialLeft : FGamepadKeyNames::SpecialRight; + + HandAxes[(int32)EOculusHandAxes::Thumb].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_ThumbPinchStrength : FOculusKeyNames::OculusHand_Right_ThumbPinchStrength; + HandAxes[(int32)EOculusHandAxes::Index].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_IndexPinchStrength : FOculusKeyNames::OculusHand_Right_IndexPinchStrength; + HandAxes[(int32)EOculusHandAxes::Middle].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_MiddlePinchStrength : FOculusKeyNames::OculusHand_Right_MiddlePinchStrength; + HandAxes[(int32)EOculusHandAxes::Ring].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_RingPinchStrength : FOculusKeyNames::OculusHand_Right_RingPinchStrength; + HandAxes[(int32)EOculusHandAxes::Pinky].Axis = (Hand == EControllerHand::Left) ? FOculusKeyNames::OculusHand_Left_PinkyPinchStrength : FOculusKeyNames::OculusHand_Right_PinkyPinchStrength; + } + + FOculusHandControllerState() + { + } +}; + + +//------------------------------------------------------------------------------------------------- +// FOculusRemoteControllerState +//------------------------------------------------------------------------------------------------- + +struct FOculusRemoteControllerState +{ + /** Button states */ + FOculusButtonState Buttons[(int32)EOculusRemoteControllerButton::TotalButtonCount]; + + FOculusRemoteControllerState() + { + for (FOculusButtonState& Button : Buttons) + { + Button.bIsPressed = false; + Button.NextRepeatTime = 0.0; + } + + Buttons[(int32)EOculusRemoteControllerButton::DPad_Up].Key = FOculusKeyNames::OculusRemote_DPad_Up; + Buttons[(int32)EOculusRemoteControllerButton::DPad_Down].Key = FOculusKeyNames::OculusRemote_DPad_Down; + Buttons[(int32)EOculusRemoteControllerButton::DPad_Left].Key = FOculusKeyNames::OculusRemote_DPad_Left; + Buttons[(int32)EOculusRemoteControllerButton::DPad_Right].Key = FOculusKeyNames::OculusRemote_DPad_Right; + Buttons[(int32)EOculusRemoteControllerButton::Enter].Key = FOculusKeyNames::OculusRemote_Enter; + Buttons[(int32)EOculusRemoteControllerButton::Back].Key = FOculusKeyNames::OculusRemote_Back; + + Buttons[(int32)EOculusRemoteControllerButton::VolumeUp].Key = FOculusKeyNames::OculusRemote_VolumeUp; + Buttons[(int32)EOculusRemoteControllerButton::VolumeDown].Key = FOculusKeyNames::OculusRemote_VolumeDown; + Buttons[(int32)EOculusRemoteControllerButton::Home].Key = FOculusKeyNames::OculusRemote_Home; + } + + void MapKeysToGamepad() + { + Buttons[(int32)EOculusRemoteControllerButton::DPad_Up].EmulatedKey = FGamepadKeyNames::DPadUp; + Buttons[(int32)EOculusRemoteControllerButton::DPad_Down].EmulatedKey = FGamepadKeyNames::DPadDown; + Buttons[(int32)EOculusRemoteControllerButton::DPad_Left].EmulatedKey = FGamepadKeyNames::DPadLeft; + Buttons[(int32)EOculusRemoteControllerButton::DPad_Right].EmulatedKey = FGamepadKeyNames::DPadRight; + Buttons[(int32)EOculusRemoteControllerButton::Enter].EmulatedKey = FGamepadKeyNames::SpecialRight; + Buttons[(int32)EOculusRemoteControllerButton::Back].EmulatedKey = FGamepadKeyNames::SpecialLeft; + } +}; + +//------------------------------------------------------------------------------------------------- +// FOculusControllerPair - A pair of Oculus controllers, hand/touch, one for either hand +//------------------------------------------------------------------------------------------------- + +struct FOculusControllerPair +{ + /** The Unreal controller index assigned to this pair */ + int32 UnrealControllerIndex; + + /** Current device state for either hand */ + FOculusTouchControllerState TouchControllerStates[2]; + + FOculusHandControllerState HandControllerStates[2]; + + FOculusControllerPair() + : UnrealControllerIndex(INDEX_NONE), + TouchControllerStates(), + HandControllerStates() + { + TouchControllerStates[(int32)EControllerHand::Left] = FOculusTouchControllerState(EControllerHand::Left); + TouchControllerStates[(int32)EControllerHand::Right] = FOculusTouchControllerState(EControllerHand::Right); + + HandControllerStates[(int32)EControllerHand::Left] = FOculusHandControllerState(EControllerHand::Left); + HandControllerStates[(int32)EControllerHand::Right] = FOculusHandControllerState(EControllerHand::Right); + } +}; + +} // namespace OculusXRInput + +#endif // OCULUS_INPUT_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRInput/Public/IOculusXRInputModule.h b/Plugins/OculusXR/Source/OculusXRInput/Public/IOculusXRInputModule.h new file mode 100644 index 0000000000000000000000000000000000000000..4a316ddb2270f278caa23034d725be8af93bd6b8 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Public/IOculusXRInputModule.h @@ -0,0 +1,56 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Modules/ModuleManager.h" +#include "IInputDeviceModule.h" + +#define OCULUS_INPUT_SUPPORTED_PLATFORMS (PLATFORM_WINDOWS && WINVER > 0x0502) || (PLATFORM_ANDROID_ARM || PLATFORM_ANDROID_ARM64) + + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IOculusXRInputModule : public IInputDeviceModule +{ + +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 IOculusXRInputModule& Get() + { + return FModuleManager::LoadModuleChecked< IOculusXRInputModule >( "OculusXRInput" ); + } + + /** + * 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( "OculusXRInput" ); + } + + /** + * Gets the number of Touch controllers that are active, so that games that require them can check to make sure they're present + * + * @return The number of Touch controllers that are active (but not necessarily tracked) + */ + virtual uint32 GetNumberOfTouchControllers() const = 0; + + /** + * Gets the number of hands that are active, so that games that require them can check to make sure they're present + * + * @return The number of Hands that are active (but not necessarily tracked) + */ + virtual uint32 GetNumberOfHandControllers() const = 0; + + virtual TSharedPtr<IInputDevice> GetInputDevice() const = 0; +}; + diff --git a/Plugins/OculusXR/Source/OculusXRInput/Public/OculusXRHandComponent.h b/Plugins/OculusXR/Source/OculusXRInput/Public/OculusXRHandComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..ebdb4eb82ceb2d1f0f957859551ab255db04121f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Public/OculusXRHandComponent.h @@ -0,0 +1,99 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "OculusXRInputFunctionLibrary.h" +#include "Components/PoseableMeshComponent.h" +#include "OculusXRHandComponent.generated.h" + +UENUM(BlueprintType) +enum class EOculusXRConfidenceBehavior : uint8 +{ + None, + HideActor +}; + +UENUM(BlueprintType) +enum class EOculusXRSystemGestureBehavior : uint8 +{ + None, + SwapMaterial +}; + +static const FQuat HandRootFixupRotation = FQuat(-0.5f, -0.5f, 0.5f, 0.5f); + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent), ClassGroup = OculusHand) +class OCULUSXRINPUT_API UOculusXRHandComponent : public UPoseableMeshComponent +{ + GENERATED_UCLASS_BODY() + +public: + virtual void BeginPlay() override; + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + /** The hand skeleton that will be loaded */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HandProperties") + EOculusXRHandType SkeletonType; + + /** The hand mesh that will be applied to the skeleton */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HandProperties") + EOculusXRHandType MeshType; + + /** Behavior for when hand tracking loses high confidence tracking */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HandProperties") + EOculusXRConfidenceBehavior ConfidenceBehavior = EOculusXRConfidenceBehavior::HideActor; + + /** Behavior for when the system gesture is actived */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HandProperties") + EOculusXRSystemGestureBehavior SystemGestureBehavior = EOculusXRSystemGestureBehavior::SwapMaterial; + + /** Material that gets applied to the hands when the system gesture is active */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HandProperties") + class UMaterialInterface* SystemGestureMaterial; + + /** Whether or not to initialize physics capsules on the skeletal mesh */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HandProperties") + bool bInitializePhysics; + + /** Whether or not the hand scale should update based on values from the runtime to match the users hand scale */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HandProperties") + bool bUpdateHandScale; + + /** Material override for the runtime skeletal mesh */ + UPROPERTY(EditDefaultsOnly, BlueprintReadWrite, Category = "HandProperties") + class UMaterialInterface* MaterialOverride; + + /** Bone mapping for custom hand skeletal meshes */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "CustomSkeletalMesh") + TMap<EOculusXRBone, FName> BoneNameMappings; + + /** List of capsule colliders created for the skeletal mesh */ + UPROPERTY(BlueprintReadOnly, Category = "HandProperties") + TArray<FOculusXRCapsuleCollider> CollisionCapsules; + + /** Whether or not the runtime skeletal mesh has been loaded and initialized */ + UPROPERTY(BlueprintReadOnly, Category = "HandProperties") + bool bSkeletalMeshInitialized = false; + +protected: + virtual void SystemGesturePressed(); + virtual void SystemGestureReleased(); + +private: + /** Whether or not this component has authority within the frame */ + bool bHasAuthority; + + /** Whether or not a custom hand mesh is being used */ + bool bCustomHandMesh = false; + + /** Whether or not the physics capsules have been initialized */ + bool bInitializedPhysics = false; + + USkeletalMesh* RuntimeSkeletalMesh; + + UMaterialInterface* CachedBaseMaterial; + + void InitializeSkeletalMesh(); + + void UpdateBonePose(); +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRInput/Public/OculusXRInputFunctionLibrary.h b/Plugins/OculusXR/Source/OculusXRInput/Public/OculusXRInputFunctionLibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..e292e4caa099ef8ca0a66fc49699800757e12473 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRInput/Public/OculusXRInputFunctionLibrary.h @@ -0,0 +1,323 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "UObject/ObjectMacros.h" +#include "Components/CapsuleComponent.h" +#include "Kismet/BlueprintFunctionLibrary.h" + +#include "OculusXRInputFunctionLibrary.generated.h" + +UENUM(BlueprintType) +enum class EOculusXRHandType : uint8 +{ + None, + HandLeft, + HandRight, +}; + +UENUM(BlueprintType) +enum class EOculusXRTrackingConfidence : uint8 +{ + Low, + High +}; + +UENUM(BlueprintType) +enum class EOculusXRFinger : uint8 +{ + Thumb, + Index, + Middle, + Ring, + Pinky, + Invalid +}; + +/** +* EOculusXRBone is enum representing the Bone Ids that come from the Oculus Runtime. +*/ +UENUM(BlueprintType) +enum class EOculusXRBone : uint8 +{ + Wrist_Root UMETA(DisplayName = "Wrist Root"), + Hand_Start = Wrist_Root UMETA(DisplayName = "Hand Start"), + Forearm_Stub UMETA(DisplayName = "Forearm Stub"), + Thumb_0 UMETA(DisplayName = "Thumb0"), + Thumb_1 UMETA(DisplayName = "Thumb1"), + Thumb_2 UMETA(DisplayName = "Thumb2"), + Thumb_3 UMETA(DisplayName = "Thumb3"), + Index_1 UMETA(DisplayName = "Index1"), + Index_2 UMETA(DisplayName = "Index2"), + Index_3 UMETA(DisplayName = "Index3"), + Middle_1 UMETA(DisplayName = "Middle1"), + Middle_2 UMETA(DisplayName = "Middle2"), + Middle_3 UMETA(DisplayName = "Middle3"), + Ring_1 UMETA(DisplayName = "Ring1"), + Ring_2 UMETA(DisplayName = "Ring2"), + Ring_3 UMETA(DisplayName = "Ring3"), + Pinky_0 UMETA(DisplayName = "Pinky0"), + Pinky_1 UMETA(DisplayName = "Pinky1"), + Pinky_2 UMETA(DisplayName = "Pinky2"), + Pinky_3 UMETA(DisplayName = "Pinky3"), + Thumb_Tip UMETA(DisplayName = "Thumb Tip"), + Max_Skinnable = Thumb_Tip UMETA(DisplayName = "Max Skinnable"), + Index_Tip UMETA(DisplayName = "Index Tip"), + Middle_Tip UMETA(DisplayName = "Middle Tip"), + Ring_Tip UMETA(DisplayName = "Ring Tip"), + Pinky_Tip UMETA(DisplayName = "Pinky Tip"), + Hand_End UMETA(DisplayName = "Hand End"), + Bone_Max = Hand_End UMETA(DisplayName = "Hand Max"), + Invalid UMETA(DisplayName = "Invalid") +}; + +/** Defines the haptics location of controller hands for tracking. */ +UENUM(BlueprintType) +enum class EOculusXRHandHapticsLocation : uint8 +{ + Hand = 0, // Haptics is applied to the whole controller + Thumb, // Haptics is applied to the thumb finger location + Index, // Haptics is applied to the index finger location + + HandHapticsLocation_Count UMETA(Hidden, DisplayName = "<INVALID>"), +}; + +struct FOculusXRHapticsDesc +{ + FOculusXRHapticsDesc( + EOculusXRHandHapticsLocation ILocation = EOculusXRHandHapticsLocation::Hand, + bool bIAppend = false) : + Location(ILocation), + bAppend(bIAppend) + {} + + void Restart() + { + Location = EOculusXRHandHapticsLocation::Hand; + bAppend = false; + } + EOculusXRHandHapticsLocation Location; + bool bAppend; +}; + +/** +* FOculusXRCapsuleCollider is a struct that contains information on the physics/collider capsules created by the runtime for hands. +* +* @var Capsule The UCapsuleComponent that is the collision capsule on the bone. Use this to register for overlap/collision events +* @var BoneIndex The Bone that this collision capsule is parented to. Corresponds to the EOculusXRBone enum. +* +*/ +USTRUCT(BlueprintType) +struct OCULUSXRINPUT_API FOculusXRCapsuleCollider +{ + GENERATED_BODY() + +public: + UPROPERTY(BlueprintReadOnly, Category = "OculusLibrary|HandTracking") + UCapsuleComponent* Capsule; + + UPROPERTY(BlueprintReadOnly, Category = "OculusLibrary|HandTracking") + EOculusXRBone BoneId; +}; + +UCLASS() +class OCULUSXRINPUT_API UOculusXRInputFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|HandTracking") + static EOculusXRFinger ConvertBoneToFinger(const EOculusXRBone Bone); + + DECLARE_MULTICAST_DELEGATE_FourParams(FHandMovementFilterDelegate, EControllerHand, FVector*, FRotator*, bool*); + static FHandMovementFilterDelegate HandMovementFilter; /// Called to modify Hand position and orientation whenever it is queried + + /** + * Creates a new runtime hand skeletal mesh. + * + * @param HandSkeletalMesh (out) Skeletal Mesh object that will be used for the runtime hand mesh + * @param SkeletonType (in) The skeleton type that will be used for generating the hand bones + * @param MeshType (in) The mesh type that will be used for generating the hand mesh + * @param WorldTometers (in) Optional change to the world to meters conversion value + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|HandTracking") + static bool GetHandSkeletalMesh(USkeletalMesh* HandSkeletalMesh, EOculusXRHandType SkeletonType, EOculusXRHandType MeshType, const float WorldToMeters = 100.0f); + + /** + * Initializes physics capsules for collision and physics on the runtime mesh + * + * @param SkeletonType (in) The skeleton type that will be used to generated the capsules + * @param HandComponent (in) The skinned mesh component that the capsules will be attached to + * @param WorldTometers (in) Optional change to the world to meters conversion value + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|HandTracking") + static TArray<FOculusXRCapsuleCollider> InitializeHandPhysics(EOculusXRHandType SkeletonType, USkinnedMeshComponent* HandComponent, const float WorldToMeters = 100.0f); + + /** + * Get the rotation of a specific bone + * + * @param DeviceHand (in) The hand to get the rotations from + * @param BoneId (in) The specific bone to get the rotation from + * @param ControllerIndex (in) Optional different controller index + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static FQuat GetBoneRotation(const EOculusXRHandType DeviceHand, const EOculusXRBone BoneId, const int32 ControllerIndex = 0); + + /** + * Get the pointer pose + * + * @param DeviceHand (in) The hand to get the pointer pose from + * @param ControllerIndex (in) Optional different controller index + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static FTransform GetPointerPose(const EOculusXRHandType DeviceHand, const int32 ControllerIndex = 0); + + /** + * Check if the pointer pose is a valid pose + * + * @param DeviceHand (in) The hand to get the pointer status from + * @param ControllerIndex (in) Optional different controller index + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static bool IsPointerPoseValid(const EOculusXRHandType DeviceHand, const int32 ControllerIndex = 0); + + /** + * Get the tracking confidence of the hand + * + * @param DeviceHand (in) The hand to get tracking confidence of + * @param ControllerIndex (in) Optional different controller index + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static EOculusXRTrackingConfidence GetTrackingConfidence(const EOculusXRHandType DeviceHand, const int32 ControllerIndex = 0); + + /** + * Get the tracking confidence of a finger + * + * @param DeviceHand (in) The hand to get tracking confidence of + * @param ControllerIndex (in) Optional different controller index + * @param Finger (in) The finger to get tracking confidence of + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static EOculusXRTrackingConfidence GetFingerTrackingConfidence(const EOculusXRHandType DeviceHand, const EOculusXRFinger Finger, const int32 ControllerIndex = 0); + + /** + * Get the scale of the hand + * + * @param DeviceHand (in) The hand to get scale of + * @param ControllerIndex (in) Optional different controller index + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static float GetHandScale(const EOculusXRHandType DeviceHand, const int32 ControllerIndex = 0); + + /** + * Get the user's dominant hand + * + * @param ControllerIndex (in) Optional different controller index + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static EOculusXRHandType GetDominantHand(const int32 ControllerIndex = 0); + + /** + * Check if hand tracking is enabled currently + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static bool IsHandTrackingEnabled(); + + /** + * Check if the hand position is valid + * + * @param DeviceHand (in) The hand to get the position from + * @param ControllerIndex (in) Optional different controller index + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static bool IsHandPositionValid(const EOculusXRHandType DeviceHand, const int32 ControllerIndex = 0); + + /** + * Get the bone name from the bone index + * + * @param BoneIndex (in) Bone index to get the name of + */ + UFUNCTION(BlueprintPure, Category = "OculusLibrary|HandTracking") + static FString GetBoneName(EOculusXRBone BoneId); + + /** + * Play a haptic feedback curve on the player's controller with location support. + * The curve data will be sampled and sent to controller to vibrate a specific location at each frame. + * @param HapticEffect The haptic effect to play + * @param Hand Which hand to play the effect on + * @param Location Which hand location to play the effect on + * @param Scale Scale between 0.0 and 1.0 on the intensity of playback + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Controller") + static void PlayCurveHapticEffect(class UHapticFeedbackEffect_Curve* HapticEffect, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand, float Scale = 1.f, bool bLoop = false); + + /** + * Play a haptic feedback buffer on the player's controller with location support. + * In each frame, the buffer data will be sampled and the individual sampled data will be sent to controller to vibrate a specific location. + * @param HapticEffect The haptic effect to play + * @param Hand Which hand to play the effect on + * @param Location Which hand location to play the effect on + * @param Scale Scale between 0.0 and 1.0 on the intensity of playback + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Controller") + static void PlayBufferHapticEffect(class UHapticFeedbackEffect_Buffer* HapticEffect, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand, float Scale = 1.f, bool bLoop = false); + + /** + * Play a haptic feedback buffer on the player's controller. + * All buffer data will be sent to controller together in one frame. + * Data duration should be no greater than controller's maximum haptics duration which can be queried with GetMaxHapticDuration. + * @param HapticEffect The haptic effect to play + * @param Hand Which hand to play the effect on + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Controller") + static void PlayAmplitudeEnvelopeHapticEffect(class UHapticFeedbackEffect_Buffer* HapticEffect, EControllerHand Hand); + + /** + * Play a haptic feedback soundwave on the player's controller. + * In each frame, the soundwave data will be splitted into a batch of data and sent to controller. + * The data duration of each frame is equal to controller's maximum haptics duration which can be queried with GetMaxHapticDuration. + * @param HapticEffect The haptic effect to play + * @param Hand Which hand to play the effect on + * @param bAppend False: any existing samples will be cleared and a new haptic effect will begin; True: samples will be appended to the currently playing effect + * @param Scale Scale between 0.0 and 1.0 on the intensity of playback + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Controller") + static void PlaySoundWaveHapticEffect(class UHapticFeedbackEffect_SoundWave* HapticEffect, EControllerHand Hand, bool bAppend = false, float Scale = 1.f, bool bLoop = false); + + /** + * Stops a playing haptic feedback curve at a specific location. + * @param HapticEffect The haptic effect to stop + * @param Hand Which hand to stop the effect for + * @param Location Which hand location to play the effect on + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Controller") + static void StopHapticEffect(EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand); + + /** + * Set the value of the haptics for the specified hand and location directly, using frequency and amplitude. NOTE: If a curve is already + * playing for this hand, it will be cancelled in favour of the specified values. + * + * @param Frequency The normalized frequency [0.0, 1.0] to play through the haptics system + * @param Amplitude The normalized amplitude [0.0, 1.0] to set the haptic feedback to + * @param Hand Which hand to play the effect on + * @param Location Which hand location to play the effect on + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Controller") + static void SetHapticsByValue(const float Frequency, const float Amplitude, EControllerHand Hand, EOculusXRHandHapticsLocation Location = EOculusXRHandHapticsLocation::Hand); + + /** + * Get the controller haptics sample rate. + * @param Hand Which hand to play the effect on + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Controller") + static float GetControllerSampleRateHz(EControllerHand Hand); + + /** + * Get the maximum duration (in seconds) that the controller haptics can handle each time. + * @param Hand Which hand to play the effect on + */ + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|Controller") + static int GetMaxHapticDuration(EControllerHand Hand); +}; + diff --git a/Plugins/OculusXR/Source/OculusXRMR/OculusXRMR.Build.cs b/Plugins/OculusXR/Source/OculusXRMR/OculusXRMR.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..f32a13a0dbd717991aff0507e81e4db7b3fdb0e6 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/OculusXRMR.Build.cs @@ -0,0 +1,66 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class OculusXRMR : ModuleRules + { + public OculusXRMR(ReadOnlyTargetRules Target) : base(Target) + { + PrivateIncludePathModuleNames.AddRange( + new string[] + { + "InputDevice", // For IInputDevice.h + "HeadMountedDisplay", // For IMotionController.h + "ImageWrapper", + "Engine" + }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "Slate", + "SlateCore", + "RHI", + "VulkanRHI", + "RenderCore", + "MediaAssets", + "HeadMountedDisplay", + "OculusXRHMD", + "OVRPluginXR" + }); + + PrivateIncludePaths.AddRange( + new string[] { + "OculusXRHMD/Private", + "OculusXRInput/Private", + }); + + PublicIncludePaths.AddRange( + new string[] { + "Runtime/Renderer/Private", + "Runtime/Engine/Classes/Components", + "Runtime/MediaAssets/Private", + }); + + if (Target.Platform == UnrealTargetPlatform.Win64) + { + PublicDelayLoadDLLs.Add("OVRPluginXR.dll"); + RuntimeDependencies.Add("$(PluginDir)/Source/ThirdParty/OVRPlugin/OVRPlugin/Lib/" + Target.Platform.ToString() + "/OVRPlugin.dll"); + } + + if (Target.Platform == UnrealTargetPlatform.Android) + { + AddEngineThirdPartyPrivateStaticDependencies(Target, "Vulkan"); + } + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + } + } +} diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRFunctionLibrary.cpp b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRFunctionLibrary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a3c35a3c6e192c946d82f1a61ae59badbe1c6b5e --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRFunctionLibrary.cpp @@ -0,0 +1,178 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRMRFunctionLibrary.h" +#include "OculusXRMRPrivate.h" +#include "OculusXRMRModule.h" +#include "OculusXRMR_CastingCameraActor.h" +#include "OculusXRMR_State.h" +#include "OculusXRHMD.h" +#include "OculusXRHMDPrivate.h" +#include "IHeadMountedDisplay.h" + +#include "GameFramework/PlayerController.h" + +//------------------------------------------------------------------------------------------------- +// UOculusXRFunctionLibrary +//------------------------------------------------------------------------------------------------- + +UOculusXRMRFunctionLibrary::UOculusXRMRFunctionLibrary(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ +} + +void UOculusXRMRFunctionLibrary::GetAllTrackedCamera(TArray<FOculusXRTrackedCamera>& TrackedCameras, bool bCalibratedOnly) +{ + TrackedCameras.Empty(); + + if (!FOculusXRMRModule::IsAvailable() || !FOculusXRMRModule::Get().IsInitialized() ) + { + UE_LOG(LogMR, Error, TEXT("OculusXRMR not available")); + return; + } + + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized() == ovrpBool_False) + { + UE_LOG(LogMR, Error, TEXT("OVRPlugin not initialized")); + return; + } + + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().UpdateExternalCamera())) + { + UE_LOG(LogMR, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().UpdateExternalCamera failure")); + return; + } + + int cameraCount = 0; + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraCount(&cameraCount))) + { + UE_LOG(LogMR, Log, TEXT("FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraCount failure")); + return; + } + + for (int i = 0; i < cameraCount; ++i) + { + char cameraName[OVRP_EXTERNAL_CAMERA_NAME_SIZE]; + ovrpCameraIntrinsics cameraIntrinsics; + ovrpCameraExtrinsics cameraExtrinsics; + FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraName(i, cameraName); + FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraIntrinsics(i, &cameraIntrinsics); + FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraExtrinsics(i, &cameraExtrinsics); + if ((bCalibratedOnly == false || cameraExtrinsics.CameraStatus == ovrpCameraStatus_Calibrated) && cameraIntrinsics.IsValid && cameraExtrinsics.IsValid) + { + FOculusXRTrackedCamera camera; + camera.Index = i; + camera.Name = cameraName; + camera.FieldOfView = FMath::RadiansToDegrees(FMath::Atan(cameraIntrinsics.FOVPort.LeftTan) + FMath::Atan(cameraIntrinsics.FOVPort.RightTan)); + camera.SizeX = cameraIntrinsics.ImageSensorPixelResolution.w; + camera.SizeY = cameraIntrinsics.ImageSensorPixelResolution.h; + camera.AttachedTrackedDevice = OculusXRHMD::ToEOculusXRTrackedDeviceType(cameraExtrinsics.AttachedToNode); + OculusXRHMD::FPose Pose; + GetOculusXRHMD()->ConvertPose(cameraExtrinsics.RelativePose, Pose); + camera.CalibratedRotation = Pose.Orientation.Rotator(); + camera.CalibratedOffset = Pose.Position; + camera.UserRotation = FRotator::ZeroRotator; + camera.UserOffset = FVector::ZeroVector; + TrackedCameras.Add(camera); + } + } +} + +OculusXRHMD::FOculusXRHMD* UOculusXRMRFunctionLibrary::GetOculusXRHMD() +{ +#if OCULUS_HMD_SUPPORTED_PLATFORMS + if (GEngine && GEngine->XRSystem.IsValid()) + { + static const FName OculusSystemName(TEXT("OculusXRHMD")); + if (GEngine->XRSystem->GetSystemName() == OculusSystemName) + { + return static_cast<OculusXRHMD::FOculusXRHMD*>(GEngine->XRSystem.Get()); + } + } +#endif + return nullptr; +} + +bool UOculusXRMRFunctionLibrary::GetTrackingReferenceLocationAndRotationInWorldSpace(USceneComponent* TrackingReferenceComponent, FVector& TRLocation, FRotator& TRRotation) +{ + if (!TrackingReferenceComponent) + { + APlayerController* PlayerController = GWorld->GetFirstPlayerController(); + if (!PlayerController) + { + return false; + } + APawn* Pawn = PlayerController->GetPawn(); + if (!Pawn) + { + return false; + } + TRLocation = Pawn->GetActorLocation(); + TRRotation = Pawn->GetActorRotation(); + return true; + } + else + { + TRLocation = TrackingReferenceComponent->GetComponentLocation(); + TRRotation = TrackingReferenceComponent->GetComponentRotation(); + return true; + } +} + +UOculusXRMR_Settings* UOculusXRMRFunctionLibrary::GetOculusXRMRSettings() +{ + UOculusXRMR_Settings* Settings = nullptr; + if (FOculusXRMRModule::IsAvailable()) + { + Settings = FOculusXRMRModule::Get().GetMRSettings(); + } + return Settings; +} + +USceneComponent* UOculusXRMRFunctionLibrary::GetTrackingReferenceComponent() +{ + USceneComponent * TrackingRef = nullptr; + if (FOculusXRMRModule::IsAvailable()) + { + TrackingRef = FOculusXRMRModule::Get().GetMRState()->TrackingReferenceComponent; + } + return TrackingRef; +} + +bool UOculusXRMRFunctionLibrary::SetTrackingReferenceComponent(USceneComponent* Component) +{ + if (FOculusXRMRModule::IsAvailable()) + { + FOculusXRMRModule::Get().GetMRState()->TrackingReferenceComponent = Component; + return true; + } + return false; +} + +float UOculusXRMRFunctionLibrary::GetMrcScalingFactor() +{ + if (FOculusXRMRModule::IsAvailable()) + { + return FOculusXRMRModule::Get().GetMRState()->ScalingFactor; + } + return 0.0; +} + +bool UOculusXRMRFunctionLibrary::SetMrcScalingFactor(float ScalingFactor) +{ + if (FOculusXRMRModule::IsAvailable() && ScalingFactor > 0.0f) + { + FOculusXRMRModule::Get().GetMRState()->ScalingFactor = ScalingFactor; + return true; + } + return false; +} + +bool UOculusXRMRFunctionLibrary::IsMrcEnabled() +{ + return FOculusXRMRModule::IsAvailable() && FOculusXRMRModule::Get().IsInitialized(); +} + +bool UOculusXRMRFunctionLibrary::IsMrcActive() +{ + return FOculusXRMRModule::IsAvailable() && FOculusXRMRModule::Get().IsActive(); +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRModule.cpp b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1241bfa6552cda8f1ecaa6ab6ecfdc07e8979b74 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRModule.cpp @@ -0,0 +1,633 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "OculusXRMRModule.h" + +#include "Engine/Engine.h" +#include "ISpectatorScreenController.h" +#include "IXRTrackingSystem.h" +#include "StereoRendering.h" +#include "StereoRenderTargetManager.h" +#include "SceneCaptureComponent2D.h" +#include "Engine/TextureRenderTarget2D.h" +#include "EngineUtils.h" +#include "PostProcess/SceneRenderTargets.h" +#include "Kismet/GameplayStatics.h" +#include "OculusXRHMDModule.h" +#include "OculusXRHMD.h" +#include "OculusXRMRFunctionLibrary.h" +#include "OculusXRMRPrivate.h" +#include "OculusXRMR_Settings.h" +#include "OculusXRMR_State.h" +#include "OculusXRMR_CastingCameraActor.h" +#include "AudioDevice.h" +#if PLATFORM_ANDROID +#include "IVulkanDynamicRHI.h" +#endif + +#if WITH_EDITOR +#include "Editor.h" // for FEditorDelegates::PostPIEStarted +#endif + +#define LOCTEXT_NAMESPACE "OculusXRMR" + +namespace +{ + ovrpCameraDevice ConvertCameraDevice(EOculusXRMR_CameraDeviceEnum device) + { + if (device == EOculusXRMR_CameraDeviceEnum::CD_WebCamera0) + return ovrpCameraDevice_WebCamera0; + else if (device == EOculusXRMR_CameraDeviceEnum::CD_WebCamera1) + return ovrpCameraDevice_WebCamera1; + checkNoEntry(); + return ovrpCameraDevice_None; + } +} + +FOculusXRMRModule::FOculusXRMRModule() + : bInitialized(false) + , MRSettings(nullptr) + , MRState(nullptr) + , MRActor(nullptr) + , CurrentWorld(nullptr) + , WorldAddedEventBinding() + , WorldDestroyedEventBinding() + , WorldLoadEventBinding() +#if PLATFORM_ANDROID + , bActivated(false) + , InitialWorldAddedEventBinding() + , InitialWorldLoadEventBinding() + , PreWorldTickEventBinding() +#endif +#if WITH_EDITOR + , PieBeginEventBinding() + , PieStartedEventBinding() + , PieEndedEventBinding() +#endif +{} + +FOculusXRMRModule::~FOculusXRMRModule() +{} + +void FOculusXRMRModule::StartupModule() +{ +#if OCULUS_MR_SUPPORTED_PLATFORMS +#if PLATFORM_WINDOWS + const TCHAR* CmdLine = FCommandLine::Get(); + const bool bAutoOpenFromParams = FParse::Param(CmdLine, TEXT("mixedreality")); + + if (bAutoOpenFromParams && FOculusXRHMDModule::Get().PreInit()) + { + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().InitializeMixedReality())) + { + InitMixedRealityCapture(); + } + else + { + UE_LOG(LogMR, Error, TEXT("ovrp_InitializeMixedReality() failed")); + } + } + else + { + UE_LOG(LogMR, Error, TEXT("OVRPlugin has not been initialized")); + } + } +#elif PLATFORM_ANDROID + // On Android, FOculusXRHMDModule::GetPluginWrapper().Media_Initialize() needs OVRPlugin to be initialized first, so we should handle that when the world is created + if (GEngine) + { + InitialWorldAddedEventBinding = GEngine->OnWorldAdded().AddRaw(this, &FOculusXRMRModule::OnInitialWorldCreated); + } + InitialWorldLoadEventBinding = FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw(this, &FOculusXRMRModule::OnInitialWorldCreated); +#endif // PLATFORM_WINDOWS || PLATFORM_ANDROID +#endif // OCULUS_MR_SUPPORTED_PLATFORMS +} + +void FOculusXRMRModule::ShutdownModule() +{ +#if OCULUS_MR_SUPPORTED_PLATFORMS + if (bInitialized) + { + if (GEngine) + { + GEngine->OnWorldAdded().Remove(WorldAddedEventBinding); + GEngine->OnWorldDestroyed().Remove(WorldDestroyedEventBinding); + FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(WorldLoadEventBinding); +#if WITH_EDITOR + FEditorDelegates::PostPIEStarted.Remove(PieStartedEventBinding); + FEditorDelegates::PrePIEEnded.Remove(PieEndedEventBinding); +#else + // Stop casting and close camera with module if it's the game + MRSettings->SetIsCasting(false); +#endif + } +#if PLATFORM_ANDROID + ovrpBool mediaInit = false; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_GetInitialized(&mediaInit)) && mediaInit == ovrpBool_True) + { + FOculusXRHMDModule::GetPluginWrapper().Media_Shutdown(); + } +#endif + FOculusXRHMDModule::GetPluginWrapper().ShutdownMixedReality(); + + if (MRSettings->IsRooted()) + { + MRSettings->RemoveFromRoot(); + } + if (MRState->IsRooted()) + { + MRState->RemoveFromRoot(); + } + } +#if PLATFORM_ANDROID + if (InitialWorldAddedEventBinding.IsValid() && GEngine) + { + GEngine->OnWorldAdded().Remove(InitialWorldAddedEventBinding); + InitialWorldAddedEventBinding.Reset(); + } + if (InitialWorldLoadEventBinding.IsValid()) + { + FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(InitialWorldLoadEventBinding); + InitialWorldLoadEventBinding.Reset(); + } +#endif +#endif // OCULUS_MR_SUPPORTED_PLATFORMS +} + +bool FOculusXRMRModule::IsActive() +{ + bool bReturn = bInitialized && MRSettings && MRSettings->GetIsCasting(); +#if PLATFORM_ANDROID + bReturn = bReturn && bActivated; +#endif + return bReturn; +} + +UOculusXRMR_Settings* FOculusXRMRModule::GetMRSettings() +{ + return MRSettings; +} + +UOculusXRMR_State* FOculusXRMRModule::GetMRState() +{ + return MRState; +} + +void FOculusXRMRModule::OnWorldCreated(UWorld* NewWorld) +{ +#if PLATFORM_WINDOWS +#if WITH_EDITORONLY_DATA + const bool bIsGameInst = !IsRunningCommandlet() && NewWorld->IsGameWorld(); + if (bIsGameInst) +#endif + { + CurrentWorld = NewWorld; + SetupInGameCapture(); + } +#endif +#if PLATFORM_ANDROID + CurrentWorld = NewWorld; + // Check MRC activation state initially when loading world + ChangeCaptureState(); + // Poll MRC activation state for future changes + PreWorldTickEventBinding = FWorldDelegates::OnWorldPreActorTick.AddRaw(this, &FOculusXRMRModule::OnWorldTick); +#endif +} + +void FOculusXRMRModule::OnWorldDestroyed(UWorld* NewWorld) +{ + CurrentWorld = nullptr; +#if PLATFORM_ANDROID + if (PreWorldTickEventBinding.IsValid()) + { + FWorldDelegates::OnWorldPreActorTick.Remove(PreWorldTickEventBinding); + PreWorldTickEventBinding.Reset(); + } +#endif // PLATFORM_ANDROID +} + +void FOculusXRMRModule::InitMixedRealityCapture() +{ + bInitialized = true; + + MRSettings = NewObject<UOculusXRMR_Settings>((UObject*)GetTransientPackage(), FName("OculusXRMR_Settings"), RF_MarkAsRootSet); + MRState = NewObject<UOculusXRMR_State>((UObject*)GetTransientPackage(), FName("OculusXRMR_State"), RF_MarkAsRootSet); + + // Always bind the event handlers in case devs call them without MRC on + MRSettings->TrackedCameraIndexChangeDelegate.BindRaw(this, &FOculusXRMRModule::OnTrackedCameraIndexChanged); + MRSettings->CompositionMethodChangeDelegate.BindRaw(this, &FOculusXRMRModule::OnCompositionMethodChanged); + MRSettings->CapturingCameraChangeDelegate.BindRaw(this, &FOculusXRMRModule::OnCapturingCameraChanged); + MRSettings->IsCastingChangeDelegate.BindRaw(this, &FOculusXRMRModule::OnIsCastingChanged); + + ResetSettingsAndState(); + + WorldAddedEventBinding = GEngine->OnWorldAdded().AddRaw(this, &FOculusXRMRModule::OnWorldCreated); + WorldDestroyedEventBinding = GEngine->OnWorldDestroyed().AddRaw(this, &FOculusXRMRModule::OnWorldDestroyed); + WorldLoadEventBinding = FCoreUObjectDelegates::PostLoadMapWithWorld.AddRaw(this, &FOculusXRMRModule::OnWorldCreated); + +#if WITH_EDITOR + // Bind events on PIE start/end to open/close camera + PieBeginEventBinding = FEditorDelegates::BeginPIE.AddRaw(this, &FOculusXRMRModule::OnPieBegin); + PieStartedEventBinding = FEditorDelegates::PostPIEStarted.AddRaw(this, &FOculusXRMRModule::OnPieStarted); + PieEndedEventBinding = FEditorDelegates::PrePIEEnded.AddRaw(this, &FOculusXRMRModule::OnPieEnded); +#else // WITH_EDITOR + // Start casting and open camera with the module if it's the game + MRSettings->SetIsCasting(true); +#endif // WITH_EDITOR +} + +void FOculusXRMRModule::SetupExternalCamera() +{ + using namespace OculusXRHMD; + + if (!MRSettings->GetIsCasting()) + { + return; + } + + // Always request the MRC actor to handle a camera state change on its end + MRState->ChangeCameraStateRequested = true; + + if (MRSettings->CompositionMethod == EOculusXRMR_CompositionMethod::ExternalComposition) + { + // Close the camera device for external composition since we don't need the actual camera feed + if (MRState->CurrentCapturingCamera != ovrpCameraDevice_None) + { + FOculusXRHMDModule::GetPluginWrapper().CloseCameraDevice(MRState->CurrentCapturingCamera); + } + } +#if PLATFORM_WINDOWS + else if (MRSettings->CompositionMethod == EOculusXRMR_CompositionMethod::DirectComposition) + { + ovrpBool available = ovrpBool_False; + if (MRSettings->CapturingCamera == EOculusXRMR_CameraDeviceEnum::CD_None) + { + MRState->CurrentCapturingCamera = ovrpCameraDevice_None; + UE_LOG(LogMR, Error, TEXT("CapturingCamera is set to CD_None which is invalid. Please pick a valid camera for CapturingCamera. If you are not sure, try to set it to CD_WebCamera0 and use the first connected USB web camera")); + return; + } + + MRState->CurrentCapturingCamera = ConvertCameraDevice(MRSettings->CapturingCamera); + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().IsCameraDeviceAvailable2(MRState->CurrentCapturingCamera, &available)) || !available) + { + MRState->CurrentCapturingCamera = ovrpCameraDevice_None; + UE_LOG(LogMR, Error, TEXT("CapturingCamera not available")); + return; + } + + ovrpSizei Size; + if (MRState->TrackedCamera.Index >= 0) + { + Size.w = MRState->TrackedCamera.SizeX; + Size.h = MRState->TrackedCamera.SizeY; + FOculusXRHMDModule::GetPluginWrapper().SetCameraDevicePreferredColorFrameSize(MRState->CurrentCapturingCamera, Size); + } + else + { + Size.w = 1280; + Size.h = 720; + FOculusXRHMDModule::GetPluginWrapper().SetCameraDevicePreferredColorFrameSize(MRState->CurrentCapturingCamera, Size); + } + + ovrpBool cameraOpen; + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().HasCameraDeviceOpened2(MRState->CurrentCapturingCamera, &cameraOpen)) || (!cameraOpen && OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().OpenCameraDevice(MRState->CurrentCapturingCamera)))) + { + MRState->CurrentCapturingCamera = ovrpCameraDevice_None; + UE_LOG(LogMR, Error, TEXT("Cannot open CapturingCamera")); + return; + } + } +#endif // PLATFORM_WINDOWS +} + +void FOculusXRMRModule::CloseExternalCamera() +{ + if (MRState->CurrentCapturingCamera != ovrpCameraDevice_None) + { + FOculusXRHMDModule::GetPluginWrapper().CloseCameraDevice(MRState->CurrentCapturingCamera); + MRState->CurrentCapturingCamera = ovrpCameraDevice_None; + } +} + +void FOculusXRMRModule::SetupInGameCapture() +{ + // Don't do anything if we don't have a UWorld or if we are not casting + if (CurrentWorld == nullptr || !MRSettings->GetIsCasting()) + { + return; + } + + // Set the bind camera request to true + MRState->BindToTrackedCameraIndexRequested = true; + + // Don't add another actor if there's already a MRC camera actor + for (TActorIterator<AOculusXRMR_CastingCameraActor> ActorIt(CurrentWorld); ActorIt; ++ActorIt) + { + if (IsValidChecked(*ActorIt) && !ActorIt->IsUnreachable() && ActorIt->IsValidLowLevel()) + { + MRActor = *ActorIt; + return; + } + } + + // Spawn an MRC camera actor if one wasn't already there + MRActor = CurrentWorld->SpawnActorDeferred<AOculusXRMR_CastingCameraActor>(AOculusXRMR_CastingCameraActor::StaticClass(), FTransform::Identity); + MRActor->InitializeStates(MRSettings, MRState); + UGameplayStatics::FinishSpawningActor(MRActor, FTransform::Identity); +} + +void FOculusXRMRModule::CloseInGameCapture() +{ + // Destory actor and close the camera when we turn MRC off + if (MRActor != nullptr && MRActor->GetWorld() != nullptr) + { + MRActor->Destroy(); + MRActor = nullptr; + } +} + +void FOculusXRMRModule::ResetSettingsAndState() +{ + // Reset MR State + MRState->TrackedCamera = FOculusXRTrackedCamera(); + MRState->TrackingReferenceComponent = nullptr; + MRState->CurrentCapturingCamera = ovrpCameraDevice_None; + MRState->ChangeCameraStateRequested = false; + MRState->BindToTrackedCameraIndexRequested = false; + + // Reset MR Settings + const bool bAutoOpenInExternalComposition = FParse::Param(FCommandLine::Get(), TEXT("externalcomposition")); + const bool bAutoOpenInDirectComposition = FParse::Param(FCommandLine::Get(), TEXT("directcomposition")); + MRSettings->BindToTrackedCameraIndexIfAvailable(0); + MRSettings->LoadFromIni(); + + // Save right after load to write defaults to the config if they weren't already there + MRSettings->SaveToIni(); + + if (bAutoOpenInExternalComposition) + { + MRSettings->CompositionMethod = EOculusXRMR_CompositionMethod::ExternalComposition; + } + else if (bAutoOpenInDirectComposition) + { + MRSettings->CompositionMethod = EOculusXRMR_CompositionMethod::DirectComposition; + } +} + +#if PLATFORM_ANDROID +void FOculusXRMRModule::ChangeCaptureState() +{ + ovrpBool activated; + // Set up or close in-game capture when activation state changes + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_Update()) && OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_IsMrcActivated(&activated)) && activated == ovrpBool_True) + { + if (!bActivated) + { + UE_LOG(LogMR, Log, TEXT("Activating MR Capture")) + bActivated = true; + + // UE resizes the main scene color and depth targets to the maximum dimensions of all rendertargets, + // which causes rendering issues if it doesn't match the compositor-allocated eye textures. This is + // a hacky fix by making sure that the scene capture rendertarget is no larger than the eye. + int frameWidth; + int frameHeight; + FOculusXRHMDModule::GetPluginWrapper().Media_GetMrcFrameSize(&frameWidth, &frameHeight); + uint32 maxWidth = frameWidth / 2; + uint32 maxHeight = frameHeight; + IStereoRenderTargetManager* const StereoRenderTargetManager = GEngine->StereoRenderingDevice->GetRenderTargetManager(); + if (StereoRenderTargetManager) + { + StereoRenderTargetManager->CalculateRenderTargetSize(*(FViewport*)GEngine->GameViewport->GetGameViewport(), maxWidth, maxHeight); + } + maxWidth *= 2; + frameWidth = frameWidth > maxWidth ? maxWidth : frameWidth; + frameHeight = frameHeight > maxHeight ? maxHeight : frameHeight; + FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcFrameSize(frameWidth, frameHeight); + UE_LOG(LogMR, Log, TEXT("MRC Frame width: %d height %d"), frameWidth, frameHeight); + + SetupInGameCapture(); + } + } + else + { + if (bActivated) + { + UE_LOG(LogMR, Log, TEXT("Deactivating MR Capture")) + bActivated = false; + CloseInGameCapture(); + } + } +} + +void FOculusXRMRModule::OnWorldTick(UWorld* World, ELevelTick Tick, float Delta) +{ + // Poll MRC activation state + if (CurrentWorld && World == CurrentWorld) + { + ChangeCaptureState(); + } +} + +void FOculusXRMRModule::OnInitialWorldCreated(UWorld* NewWorld) +{ + // Remove the initial world load handlers + if (InitialWorldAddedEventBinding.IsValid()) + { + GEngine->OnWorldAdded().Remove(InitialWorldAddedEventBinding); + InitialWorldAddedEventBinding.Reset(); + } + if (InitialWorldLoadEventBinding.IsValid()) + { + FCoreUObjectDelegates::PostLoadMapWithWorld.Remove(InitialWorldLoadEventBinding); + InitialWorldLoadEventBinding.Reset(); + } + + // Initialize and check if MRC is enabled + if (FOculusXRHMDModule::Get().PreInit()) + { + if (FOculusXRHMDModule::GetPluginWrapper().GetInitialized()) + { + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().InitializeMixedReality())) + { + ovrpBool mrcEnabled; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_Initialize())) + { + UE_LOG(LogMR, Log, TEXT("MRC Initialized")); + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_IsMrcEnabled(&mrcEnabled)) && mrcEnabled == ovrpBool_True) + { + UE_LOG(LogMR, Log, TEXT("MRC Enabled")); + + // Find a free queue index for vulkan + if (RHIGetInterfaceType() == ERHIInterfaceType::Vulkan) + { + unsigned int queueIndex = 0; + ExecuteOnRenderThread([&queueIndex]() + { + ExecuteOnRHIThread([&queueIndex]() + { + const uint32 GraphicsQueueIndex = GetIVulkanDynamicRHI()->RHIGetGraphicsQueueIndex(); + if (GraphicsQueueIndex == queueIndex) + { + ++queueIndex; + } + }); + }); + FOculusXRHMDModule::GetPluginWrapper().Media_SetAvailableQueueIndexVulkan(queueIndex); + } + + FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcInputVideoBufferType(ovrpMediaInputVideoBufferType_TextureHandle); + + FAudioDeviceHandle AudioDevice = FAudioDevice::GetMainAudioDevice(); + if (AudioDevice.GetAudioDevice()) + { + float SampleRate = AudioDevice->GetSampleRate(); + FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcAudioSampleRate((int)SampleRate); + } + + InitMixedRealityCapture(); + OnWorldCreated(NewWorld); + } + else + { + // Shut down if MRC not enabled or the media couldn't be enabled + FOculusXRHMDModule::GetPluginWrapper().Media_Shutdown(); + FOculusXRHMDModule::GetPluginWrapper().ShutdownMixedReality(); + } + } + else + { + // Shut down if MRC not enabled or the media couldn't be enabled + FOculusXRHMDModule::GetPluginWrapper().ShutdownMixedReality(); + } + } + else + { + UE_LOG(LogMR, Error, TEXT("ovrp_InitializeMixedReality() failed")); + } + } + else + { + UE_LOG(LogMR, Error, TEXT("OVRPlugin has not been initialized")); + } + } +} +#endif + +void FOculusXRMRModule::OnTrackedCameraIndexChanged(int OldVal, int NewVal) +{ + if (OldVal == NewVal) + { + return; + } + MRState->BindToTrackedCameraIndexRequested = true; +} + +void FOculusXRMRModule::OnCompositionMethodChanged(EOculusXRMR_CompositionMethod OldVal, EOculusXRMR_CompositionMethod NewVal) +{ + if (OldVal == NewVal) + { + return; + } + SetupExternalCamera(); +} + +void FOculusXRMRModule::OnCapturingCameraChanged(EOculusXRMR_CameraDeviceEnum OldVal, EOculusXRMR_CameraDeviceEnum NewVal) +{ + if (OldVal == NewVal) + { + return; + } + + // Close the old camera device before switching + if (OldVal != EOculusXRMR_CameraDeviceEnum::CD_None) + { + auto CameraDevice = ConvertCameraDevice(OldVal); + FOculusXRHMDModule::GetPluginWrapper().CloseCameraDevice(CameraDevice); + } + SetupExternalCamera(); +} + +void FOculusXRMRModule::OnIsCastingChanged(bool OldVal, bool NewVal) +{ + if (OldVal == NewVal) + { + return; + } + if (NewVal == true) + { +#if PLATFORM_ANDROID + FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcActivationMode(ovrpMediaMrcActivationMode_Automatic); +#endif + // Initialize everything again if we turn MRC on + SetupExternalCamera(); + SetupInGameCapture(); + } + else + { +#if PLATFORM_ANDROID + FOculusXRHMDModule::GetPluginWrapper().Media_SetMrcActivationMode(ovrpMediaMrcActivationMode_Disabled); +#endif + CloseInGameCapture(); + CloseExternalCamera(); + } +} + +void FOculusXRMRModule::OnUseDynamicLightingChanged(bool OldVal, bool NewVal) +{ + if (OldVal == NewVal) + { + return; + } + SetupExternalCamera(); +} + +void FOculusXRMRModule::OnDepthQualityChanged(EOculusXRMR_DepthQuality OldVal, EOculusXRMR_DepthQuality NewVal) +{ + if (OldVal == NewVal) + { + return; + } + SetupExternalCamera(); +} + +#if WITH_EDITOR +void FOculusXRMRModule::OnPieBegin(bool bIsSimulating) +{ + // Reset all the parameters and start casting when PIE starts but before the game is initialized + if (!bIsSimulating) + { + + ResetSettingsAndState(); + + // Always start casting with PIE (since this can only be reached if the command line param is on) + MRSettings->SetIsCasting(true); + } +} + +void FOculusXRMRModule::OnPieStarted(bool bIsSimulating) +{ + // Handle the PIE world as a normal game world + UWorld* PieWorld = GEditor->GetPIEWorldContext()->World(); + if (!bIsSimulating && PieWorld) + { + OnWorldCreated(PieWorld); + } +} + +void FOculusXRMRModule::OnPieEnded(bool bIsSimulating) +{ + UWorld* PieWorld = GEditor->GetPIEWorldContext()->World(); + if (!bIsSimulating && PieWorld) + { + // Stop casting when PIE ends + MRSettings->SetIsCasting(false); + OnWorldDestroyed(PieWorld); + } +} +#endif // WITH_EDITOR + +IMPLEMENT_MODULE( FOculusXRMRModule, OculusXRMR ) + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRModule.h b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRModule.h new file mode 100644 index 0000000000000000000000000000000000000000..acc3c3c650dd98d5e711e48acf29b85c9fb62c69 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRModule.h @@ -0,0 +1,101 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "IOculusXRMRModule.h" +#include "Engine/EngineBaseTypes.h" + +#define LOCTEXT_NAMESPACE "OculusXRMR" + +enum class EOculusXRMR_CompositionMethod : uint8; +enum class EOculusXRMR_CameraDeviceEnum : uint8; +enum class EOculusXRMR_DepthQuality : uint8; + +class UOculusXRMR_Settings; +class AOculusXRMR_CastingCameraActor; +class UOculusXRMR_State; + +//------------------------------------------------------------------------------------------------- +// FOculusXRInputModule +//------------------------------------------------------------------------------------------------- + +class FOculusXRMRModule : public IOculusXRMRModule +{ +public: + FOculusXRMRModule(); + ~FOculusXRMRModule(); + + static inline FOculusXRMRModule& Get() + { + return FModuleManager::GetModuleChecked< FOculusXRMRModule >("OculusXRMR"); + } + + // IOculusXRMRModule + virtual void StartupModule() override; + virtual void ShutdownModule() override; + + bool IsInitialized() { return bInitialized; } + + bool IsActive(); + UOculusXRMR_Settings* GetMRSettings(); + UOculusXRMR_State* GetMRState(); + +private: + bool bInitialized; + UOculusXRMR_Settings* MRSettings; + UOculusXRMR_State* MRState; + AOculusXRMR_CastingCameraActor* MRActor; + UWorld* CurrentWorld; + + FDelegateHandle WorldAddedEventBinding; + FDelegateHandle WorldDestroyedEventBinding; + FDelegateHandle WorldLoadEventBinding; + + void InitMixedRealityCapture(); + + /** Initialize the tracked physical camera */ + void SetupExternalCamera(); + /** Close the tracked physical camera */ + void CloseExternalCamera(); + /** Set up the needed settings and actors for MRC in-game */ + void SetupInGameCapture(); + /** Destroy actors for MRC in-game */ + void CloseInGameCapture(); + /** Reset all the MRC settings and state to the config and default */ + void ResetSettingsAndState(); + + /** Handle changes on specific settings */ + void OnCompositionMethodChanged(EOculusXRMR_CompositionMethod OldVal, EOculusXRMR_CompositionMethod NewVal); + void OnCapturingCameraChanged(EOculusXRMR_CameraDeviceEnum OldVal, EOculusXRMR_CameraDeviceEnum NewVal); + void OnIsCastingChanged(bool OldVal, bool NewVal); + void OnUseDynamicLightingChanged(bool OldVal, bool NewVal); + void OnDepthQualityChanged(EOculusXRMR_DepthQuality OldVal, EOculusXRMR_DepthQuality NewVal); + void OnTrackedCameraIndexChanged(int OldVal, int NewVal); + + void OnWorldCreated(UWorld* NewWorld); + void OnWorldDestroyed(UWorld* NewWorld); + +#if PLATFORM_ANDROID + bool bActivated; + + FDelegateHandle InitialWorldAddedEventBinding; + FDelegateHandle InitialWorldLoadEventBinding; + FDelegateHandle PreWorldTickEventBinding; + + void ChangeCaptureState(); + void OnWorldTick(UWorld* World, ELevelTick Tick, float Delta); + void OnInitialWorldCreated(UWorld* NewWorld); +#endif + +#if WITH_EDITOR + FDelegateHandle PieBeginEventBinding; + FDelegateHandle PieStartedEventBinding; + FDelegateHandle PieEndedEventBinding; + + void OnPieBegin(bool bIsSimulating); + void OnPieStarted(bool bIsSimulating); + void OnPieEnded(bool bIsSimulating); +#endif +}; + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRPrivate.h b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRPrivate.h new file mode 100644 index 0000000000000000000000000000000000000000..a14938f05d1ba3495ec8d21018b2e91ade44b402 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMRPrivate.h @@ -0,0 +1,11 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "IOculusXRMRModule.h" +#include "CoreMinimal.h" + +#include "OculusXRPluginWrapper.h" + +#if OCULUS_MR_SUPPORTED_PLATFORMS +DEFINE_LOG_CATEGORY_STATIC(LogMR, Log, All); +#endif // OCULUS_MR_SUPPORTED_PLATFORMS diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_CastingCameraActor.cpp b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_CastingCameraActor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..70023eff089b45f07b62398e23a77e996a3698f5 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_CastingCameraActor.cpp @@ -0,0 +1,1039 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRMR_CastingCameraActor.h" + +#include "OculusXRMRPrivate.h" +#include "OculusXRHMD_Settings.h" +#include "OculusXRHMD.h" +#include "OculusXRHMD_SpectatorScreenController.h" +#include "OculusXRMRModule.h" +#include "OculusXRMR_Settings.h" +#include "OculusXRMR_State.h" +#include "OculusXRMR_PlaneMeshComponent.h" +#include "OculusXRMRFunctionLibrary.h" +#include "Components/StaticMeshComponent.h" +#include "Components/SceneCaptureComponent2D.h" +#include "UObject/ConstructorHelpers.h" +#include "Engine/Engine.h" +#include "Engine/World.h" +#include "GameFramework/PlayerController.h" +#include "GameFramework/WorldSettings.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Rendering/Texture2DResource.h" +#include "RenderingThread.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "VRNotificationsComponent.h" +#include "RenderUtils.h" +#include "AudioDevice.h" + +#define LOCTEXT_NAMESPACE "OculusXRMR_CastingCameraActor" + +// Possibly add 2=Limited in a future update +static TAutoConsoleVariable<int32> CEnableExternalCompositionPostProcess(TEXT("oculus.mr.ExternalCompositionPostProcess"), 0, TEXT("Enable MR external composition post process: 0=Off, 1=On")); +static TAutoConsoleVariable<int32> COverrideMixedRealityParametersVar(TEXT("oculus.mr.OverrideParameters"), 0, TEXT("Use the Mixed Reality console variables")); +static TAutoConsoleVariable<int32> CChromaKeyColorRVar(TEXT("oculus.mr.ChromaKeyColor_R"), 0, TEXT("Chroma Key Color R")); +static TAutoConsoleVariable<int32> CChromaKeyColorGVar(TEXT("oculus.mr.ChromaKeyColor_G"), 255, TEXT("Chroma Key Color G")); +static TAutoConsoleVariable<int32> CChromaKeyColorBVar(TEXT("oculus.mr.ChromaKeyColor_B"), 0, TEXT("Chroma Key Color B")); +static TAutoConsoleVariable<float> CChromaKeySimilarityVar(TEXT("oculus.mr.ChromaKeySimilarity"), 0.6f, TEXT("Chroma Key Similarity")); +static TAutoConsoleVariable<float> CChromaKeySmoothRangeVar(TEXT("oculus.mr.ChromaKeySmoothRange"), 0.03f, TEXT("Chroma Key Smooth Range")); +static TAutoConsoleVariable<float> CChromaKeySpillRangeVar(TEXT("oculus.mr.ChromaKeySpillRange"), 0.04f, TEXT("Chroma Key Spill Range")); +static TAutoConsoleVariable<float> CCastingLantencyVar(TEXT("oculus.mr.CastingLantency"), 0, TEXT("Casting Latency")); + +namespace +{ + bool GetCameraTrackedObjectPoseInTrackingSpace(OculusXRHMD::FOculusXRHMD* OculusXRHMD, const FOculusXRTrackedCamera& TrackedCamera, OculusXRHMD::FPose& CameraTrackedObjectPose) + { + using namespace OculusXRHMD; + + CameraTrackedObjectPose = FPose(FQuat::Identity, FVector::ZeroVector); + + if (TrackedCamera.AttachedTrackedDevice != EOculusXRTrackedDeviceType::None) + { + ovrpResult result = ovrpSuccess; + ovrpPoseStatef cameraPoseState; + ovrpNode deviceNode = ToOvrpNode(TrackedCamera.AttachedTrackedDevice); + ovrpBool nodePresent = ovrpBool_False; + result = FOculusXRHMDModule::GetPluginWrapper().GetNodePresent2(deviceNode, &nodePresent); + if (!OVRP_SUCCESS(result)) + { + UE_LOG(LogMR, Warning, TEXT("Unable to check if AttachedTrackedDevice is present")); + return false; + } + if (!nodePresent) + { + UE_LOG(LogMR, Warning, TEXT("AttachedTrackedDevice is not present")); + return false; + } + + OculusXRHMD::FGameFrame* CurrentFrame; + if (IsInGameThread()) + { + CurrentFrame = OculusXRHMD->GetNextFrameToRender(); + } + else + { + CurrentFrame = OculusXRHMD->GetFrame_RenderThread(); + } + + result = CurrentFrame ? FOculusXRHMDModule::GetPluginWrapper().GetNodePoseState3(ovrpStep_Render, CurrentFrame->FrameNumber, deviceNode, &cameraPoseState) : ovrpFailure; + if (!OVRP_SUCCESS(result)) + { + UE_LOG(LogMR, Warning, TEXT("Unable to retrieve AttachedTrackedDevice pose state")); + return false; + } + OculusXRHMD->ConvertPose(cameraPoseState.Pose, CameraTrackedObjectPose); + } + + return true; + } +} + +////////////////////////////////////////////////////////////////////////// +// ACastingCameraActor + +AOculusXRMR_CastingCameraActor::AOculusXRMR_CastingCameraActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , ChromaKeyMaterial(NULL) + , ChromaKeyMaterialInstance(NULL) + , CameraFrameMaterialInstance(NULL) + , TrackedCameraCalibrationRequired(false) + , HasTrackedCameraCalibrationCalibrated(false) + , RefreshBoundaryMeshCounter(3) + , ForegroundLayerBackgroundColor(FColor::Green) + , ForegroundMaxDistance(300.0f) +{ + PrimaryActorTick.bCanEverTick = true; + PrimaryActorTick.bTickEvenWhenPaused = true; + + VRNotificationComponent = CreateDefaultSubobject<UVRNotificationsComponent>(TEXT("VRNotificationComponent")); + +#if PLATFORM_WINDOWS + PlaneMeshComponent = CreateDefaultSubobject<UOculusXRMR_PlaneMeshComponent>(TEXT("PlaneMeshComponent")); + PlaneMeshComponent->AttachToComponent(GetRootComponent(), FAttachmentTransformRules::KeepRelativeTransform); + PlaneMeshComponent->ResetRelativeTransform(); + PlaneMeshComponent->SetVisibility(false); +#endif + + ChromaKeyMaterial = Cast<UMaterial>(StaticLoadObject(UMaterial::StaticClass(), NULL, TEXT("/OculusXR/Materials/OculusMR_ChromaKey"))); + if (!ChromaKeyMaterial) + { + UE_LOG(LogMR, Warning, TEXT("Invalid ChromaKeyMaterial")); + } + + OpaqueColoredMaterial = Cast<UMaterial>(StaticLoadObject(UMaterial::StaticClass(), NULL, TEXT("/OculusXR/Materials/OculusMR_OpaqueColoredMaterial"))); + if (!OpaqueColoredMaterial) + { + UE_LOG(LogMR, Warning, TEXT("Invalid OpaqueColoredMaterial")); + } + + // Structure to hold one-time initialization + struct FConstructorStatics + { + ConstructorHelpers::FObjectFinder<UTexture2D> WhiteSquareTexture; + + FConstructorStatics() + : WhiteSquareTexture(TEXT("/Engine/EngineResources/WhiteSquareTexture")) + { + } + }; + static FConstructorStatics ConstructorStatics; + + DefaultTexture_White = ConstructorStatics.WhiteSquareTexture.Object; + check(DefaultTexture_White); + + ForegroundCaptureActor = nullptr; + + // Set the render targets for background and foreground to copies of the default texture +#if PLATFORM_WINDOWS + BackgroundRenderTargets.SetNum(1); + ForegroundRenderTargets.SetNum(1); + + BackgroundRenderTargets[0] = NewObject<UTextureRenderTarget2D>(); + BackgroundRenderTargets[0]->RenderTargetFormat = RTF_RGBA8_SRGB; + + ForegroundRenderTargets[0] = NewObject<UTextureRenderTarget2D>(); + ForegroundRenderTargets[0]->RenderTargetFormat = RTF_RGBA8_SRGB; +#elif PLATFORM_ANDROID + BackgroundRenderTargets.SetNum(NumRTs); + ForegroundRenderTargets.SetNum(NumRTs); + AudioBuffers.SetNum(NumRTs); + AudioTimes.SetNum(NumRTs); + PoseTimes.SetNum(NumRTs); + + for (unsigned int i = 0; i < NumRTs; ++i) + { + BackgroundRenderTargets[i] = NewObject<UTextureRenderTarget2D>(); + BackgroundRenderTargets[i]->RenderTargetFormat = RTF_RGBA8_SRGB; + + ForegroundRenderTargets[i] = NewObject<UTextureRenderTarget2D>(); + ForegroundRenderTargets[i]->RenderTargetFormat = RTF_RGBA8_SRGB; + + AudioTimes[i] = 0.0; + PoseTimes[i] = 0.0; + } + + SyncId = -1; + RenderedRTs = 0; + CaptureIndex = 0; +#endif +} + +void AOculusXRMR_CastingCameraActor::BeginDestroy() +{ + CloseTrackedCamera(); + Super::BeginDestroy(); +} + +bool AOculusXRMR_CastingCameraActor::RefreshExternalCamera() +{ + using namespace OculusXRHMD; + if (MRState->TrackedCamera.Index >= 0) + { + int cameraCount; + if (OVRP_FAILURE(FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraCount(&cameraCount))) + { + cameraCount = 0; + } + if (MRState->TrackedCamera.Index >= cameraCount) + { + UE_LOG(LogMR, Error, TEXT("Invalid TrackedCamera Index")); + return false; + } + FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr; + if (!OculusXRHMD) + { + UE_LOG(LogMR, Error, TEXT("Unable to retrieve OculusXRHMD")); + return false; + } + ovrpResult result = ovrpSuccess; + ovrpCameraExtrinsics cameraExtrinsics; + result = FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraExtrinsics(MRState->TrackedCamera.Index, &cameraExtrinsics); + if (OVRP_FAILURE(result)) + { + UE_LOG(LogMR, Error, TEXT("FOculusXRHMDModule::GetPluginWrapper().GetExternalCameraExtrinsics failed")); + return false; + } + MRState->TrackedCamera.AttachedTrackedDevice = OculusXRHMD::ToEOculusXRTrackedDeviceType(cameraExtrinsics.AttachedToNode); + OculusXRHMD::FPose Pose; + OculusXRHMD->ConvertPose(cameraExtrinsics.RelativePose, Pose); + MRState->TrackedCamera.CalibratedRotation = Pose.Orientation.Rotator(); + MRState->TrackedCamera.CalibratedOffset = Pose.Position; + MRState->TrackedCamera.UpdateTime = cameraExtrinsics.LastChangedTimeSeconds; + } + + return true; +} + +void AOculusXRMR_CastingCameraActor::BeginPlay() +{ + Super::BeginPlay(); + + SetupTrackedCamera(); + RequestTrackedCameraCalibration(); + SetupMRCScreen(); + + FScriptDelegate Delegate; + Delegate.BindUFunction(this, FName(TEXT("OnHMDRecentered"))); + VRNotificationComponent->HMDRecenteredDelegate.Add(Delegate); + +#if PLATFORM_ANDROID + FAudioDeviceHandle AudioDevice = FAudioDevice::GetMainAudioDevice(); + if (AudioDevice.GetAudioDevice()) + { + AudioDevice->StartRecording(nullptr, 0.1); + } +#endif +} + +void AOculusXRMR_CastingCameraActor::EndPlay(EEndPlayReason::Type Reason) +{ +#if PLATFORM_ANDROID + FAudioDeviceHandle AudioDevice = FAudioDevice::GetMainAudioDevice(); + if (AudioDevice.GetAudioDevice()) + { + float NumChannels = 2; + float SampleRate = AudioDevice->GetSampleRate(); + AudioDevice->StopRecording(nullptr, NumChannels, SampleRate); + } +#endif + + VRNotificationComponent->HMDRecenteredDelegate.Remove(this, FName(TEXT("OnHMDRecentered"))); + + MRState->TrackingReferenceComponent = nullptr; + + CloseMRCScreen(); + + CloseTrackedCamera(); + Super::EndPlay(Reason); +} + +void AOculusXRMR_CastingCameraActor::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); + + if (MRState->BindToTrackedCameraIndexRequested) + { + Execute_BindToTrackedCameraIndexIfAvailable(); + } + + if (!RefreshExternalCamera()) + { + CloseTrackedCamera(); + return; + } + + if (COverrideMixedRealityParametersVar.GetValueOnAnyThread() > 0) + { + MRSettings->ChromaKeyColor = FColor(CChromaKeyColorRVar.GetValueOnAnyThread(), CChromaKeyColorGVar.GetValueOnAnyThread(), CChromaKeyColorBVar.GetValueOnAnyThread()); + MRSettings->ChromaKeySimilarity = CChromaKeySimilarityVar.GetValueOnAnyThread(); + MRSettings->ChromaKeySmoothRange = CChromaKeySmoothRangeVar.GetValueOnAnyThread(); + MRSettings->ChromaKeySpillRange = CChromaKeySpillRangeVar.GetValueOnAnyThread(); + MRSettings->CastingLatency = CCastingLantencyVar.GetValueOnAnyThread(); + } + + // Reset capturing components if the composition method changes + if (MRState->ChangeCameraStateRequested) + { + CloseTrackedCamera(); + CloseMRCScreen(); + SetupTrackedCamera(); + SetupMRCScreen(); + } + +#if PLATFORM_WINDOWS + if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::ExternalComposition) +#endif + { + if (ForegroundLayerBackgroundColor != MRSettings->BackdropColor) + { + ForegroundLayerBackgroundColor = MRSettings->BackdropColor; + SetBackdropMaterialColor(); + } + // Enable external composition post process based on setting + bool bPostProcess = MRSettings->ExternalCompositionPostProcessEffects != EOculusXRMR_PostProcessEffects::PPE_Off; + if (COverrideMixedRealityParametersVar.GetValueOnAnyThread() > 0) + { + bPostProcess = CEnableExternalCompositionPostProcess.GetValueOnAnyThread() > 0; + } + GetCaptureComponent2D()->ShowFlags.PostProcessing = bPostProcess; + if (ForegroundCaptureActor) + { + ForegroundCaptureActor->GetCaptureComponent2D()->ShowFlags.PostProcessing = bPostProcess; + } + } +#if PLATFORM_WINDOWS + else if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::DirectComposition) + { + SetupCameraFrameMaterialInstance(); + + if (CameraFrameMaterialInstance) + { + CameraFrameMaterialInstance->SetVectorParameterValue(FName(TEXT("ChromaKeyColor")), FLinearColor(MRSettings->ChromaKeyColor)); + CameraFrameMaterialInstance->SetScalarParameterValue(FName(TEXT("ChromaKeySimilarity")), MRSettings->ChromaKeySimilarity); + CameraFrameMaterialInstance->SetScalarParameterValue(FName(TEXT("ChromaKeySmoothRange")), MRSettings->ChromaKeySmoothRange); + CameraFrameMaterialInstance->SetScalarParameterValue(FName(TEXT("ChromaKeySpillRange")), MRSettings->ChromaKeySpillRange); + } + } +#endif + + if (MRState->CurrentCapturingCamera != ovrpCameraDevice_None) + { + ovrpBool colorFrameAvailable = ovrpBool_False; + ovrpSizei colorFrameSize = { 0, 0 }; + const ovrpByte* colorFrameData = nullptr; + int colorRowPitch = 0; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().IsCameraDeviceColorFrameAvailable2(MRState->CurrentCapturingCamera, &colorFrameAvailable)) && colorFrameAvailable && + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetCameraDeviceColorFrameSize(MRState->CurrentCapturingCamera, &colorFrameSize)) && + OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().GetCameraDeviceColorFrameBgraPixels(MRState->CurrentCapturingCamera, &colorFrameData, &colorRowPitch))) + { + UpdateCameraColorTexture(colorFrameSize, colorFrameData, colorRowPitch); + } + } + + if (TrackedCameraCalibrationRequired) + { + CalibrateTrackedCameraPose(); + } + + UpdateTrackedCameraPosition(); + +#if PLATFORM_WINDOWS + RepositionPlaneMesh(); + + double HandPoseStateLatencyToSet = (double)MRSettings->HandPoseStateLatency; + ovrpResult result = FOculusXRHMDModule::GetPluginWrapper().SetHandNodePoseStateLatency(HandPoseStateLatencyToSet); + if (OVRP_FAILURE(result)) + { + UE_LOG(LogMR, Warning, TEXT("FOculusXRHMDModule::GetPluginWrapper().SetHandNodePoseStateLatency(%f) failed, result %d"), HandPoseStateLatencyToSet, (int)result); + } +#endif + + UpdateRenderTargetSize(); + +#if PLATFORM_ANDROID + OculusXRHMD::FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (OculusXRHMD::FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr; + if (OculusXRHMD) + { + ovrpPosef OvrpPose, OvrpHeadPose, OvrpLeftHandPose, OvrpRightHandPose; + FOculusXRHMDModule::GetPluginWrapper().GetTrackingTransformRelativePose(&OvrpPose, ovrpTrackingOrigin_Stage); + OculusXRHMD::FPose StageToLocalPose; + OculusXRHMD->ConvertPose(OvrpPose, StageToLocalPose); + OculusXRHMD::FPose LocalToStagePose = StageToLocalPose.Inverse(); + + OculusXRHMD::FPose HeadPose; + OculusXRHMD->GetCurrentPose(OculusXRHMD::ToExternalDeviceId(ovrpNode_Head), HeadPose.Orientation, HeadPose.Position); + HeadPose = LocalToStagePose * HeadPose; + OculusXRHMD->ConvertPose(HeadPose, OvrpHeadPose); + + OculusXRHMD::FPose LeftHandPose; + OculusXRHMD->GetCurrentPose(OculusXRHMD::ToExternalDeviceId(ovrpNode_HandLeft), HeadPose.Orientation, HeadPose.Position); + LeftHandPose = LocalToStagePose * LeftHandPose; + OculusXRHMD->ConvertPose(LeftHandPose, OvrpLeftHandPose); + + OculusXRHMD::FPose RightHandPose; + OculusXRHMD->GetCurrentPose(OculusXRHMD::ToExternalDeviceId(ovrpNode_HandRight), HeadPose.Orientation, HeadPose.Position); + RightHandPose = LocalToStagePose * RightHandPose; + OculusXRHMD->ConvertPose(RightHandPose, OvrpRightHandPose); + + FOculusXRHMDModule::GetPluginWrapper().Media_SetHeadsetControllerPose(OvrpHeadPose, OvrpLeftHandPose, OvrpRightHandPose); + } + + // Alternate foreground and background captures by nulling the capture component texture target + if (GetCaptureComponent2D()->IsVisible()) + { + GetCaptureComponent2D()->SetVisibility(false); + + // Encode a texture the frame before we render to it again to ensure completed render at the cost of latency + unsigned int EncodeIndex = (CaptureIndex + 1) % NumRTs; + + // Skip encoding for the first few frames before they have completed rendering + if (RenderedRTs > EncodeIndex) + { + FOculusXRHMDModule::GetPluginWrapper().Media_SyncMrcFrame(SyncId); + + int NumChannels = 2; + double AudioTime = AudioTimes[EncodeIndex]; + void* BackgroundTexture; + void* ForegroundTexture; + + if (IsVulkanPlatform(GMaxRHIShaderPlatform)) + { + ExecuteOnRenderThread([this, EncodeIndex, &BackgroundTexture, &ForegroundTexture]() + { + ExecuteOnRHIThread([this, EncodeIndex, &BackgroundTexture, &ForegroundTexture]() + { + // The Vulkan RHI's implementation of GetNativeResource is different and returns the VkImage cast + // as a void* instead of a pointer to the VkImage, so we need this workaround + BackgroundTexture = (void*)BackgroundRenderTargets[EncodeIndex]->GetResource()->TextureRHI->GetNativeResource(); + ForegroundTexture = (void*)ForegroundRenderTargets[EncodeIndex]->GetResource()->TextureRHI->GetNativeResource(); + }); + }); + } + else + { + ExecuteOnRenderThread([this, EncodeIndex, &BackgroundTexture, &ForegroundTexture]() + { + ExecuteOnRHIThread([this, EncodeIndex, &BackgroundTexture, &ForegroundTexture]() + { + BackgroundTexture = *((void**)BackgroundRenderTargets[EncodeIndex]->GetResource()->TextureRHI->GetNativeResource()); + ForegroundTexture = *((void**)ForegroundRenderTargets[EncodeIndex]->GetResource()->TextureRHI->GetNativeResource()); + }); + }); + } + FOculusXRHMDModule::GetPluginWrapper().Media_EncodeMrcFrameDualTexturesWithPoseTime(BackgroundTexture, ForegroundTexture, AudioBuffers[EncodeIndex].GetData(), AudioBuffers[EncodeIndex].Num() * sizeof(float), NumChannels, AudioTime, PoseTimes[CaptureIndex], &SyncId); + } + ForegroundCaptureActor->GetCaptureComponent2D()->SetVisibility(true); + } + else if (ForegroundCaptureActor && ForegroundCaptureActor->GetCaptureComponent2D()->IsVisible()) + { + ForegroundCaptureActor->GetCaptureComponent2D()->SetVisibility(false); + + // Increment scene captures to next texture + CaptureIndex = (CaptureIndex + 1) % NumRTs; + GetCaptureComponent2D()->TextureTarget = BackgroundRenderTargets[CaptureIndex]; + ForegroundCaptureActor->GetCaptureComponent2D()->TextureTarget = ForegroundRenderTargets[CaptureIndex]; + GetCaptureComponent2D()->SetVisibility(true); + + FAudioDeviceHandle AudioDevice = FAudioDevice::GetMainAudioDevice(); + if (AudioDevice.GetAudioDevice()) + { + float NumChannels, SampleRate; + NumChannels = 2; + SampleRate = AudioDevice->GetSampleRate(); + AudioBuffers[CaptureIndex] = AudioDevice->StopRecording(nullptr, NumChannels, SampleRate); + AudioTimes[CaptureIndex] = AudioDevice->GetAudioTime(); + //UE_LOG(LogMR, Error, TEXT("SampleRate: %f, NumChannels: %f, Time: %f, Buffer Length: %d, Buffer: %p"), SampleRate, NumChannels, AudioDevice->GetAudioTime(), AudioBuffers[EncodeIndex].Num(), AudioBuffers[EncodeIndex].GetData()); + AudioDevice->StartRecording(nullptr, 0.1); + } + + //PoseTimes[CaptureIndex] = MRState->TrackedCamera.UpdateTime; + + // Increment this counter for the initial cycle through "swapchain" + if (RenderedRTs < NumRTs) + { + RenderedRTs++; + } + } +#endif +} + +void AOculusXRMR_CastingCameraActor::UpdateCameraColorTexture(const ovrpSizei &frameSize, const ovrpByte* frameData, int rowPitch) +{ + if (CameraColorTexture->GetSizeX() != frameSize.w || CameraColorTexture->GetSizeY() != frameSize.h) + { + UE_LOG(LogMR, Log, TEXT("CameraColorTexture resize to (%d, %d)"), frameSize.w, frameSize.h); + CameraColorTexture = UTexture2D::CreateTransient(frameSize.w, frameSize.h); + CameraColorTexture->UpdateResource(); + if (CameraFrameMaterialInstance) + { + CameraFrameMaterialInstance->SetTextureParameterValue(FName(TEXT("CameraCaptureTexture")), CameraColorTexture); + CameraFrameMaterialInstance->SetVectorParameterValue(FName(TEXT("CameraCaptureTextureSize")), + FLinearColor((float)CameraColorTexture->GetSizeX(), (float)CameraColorTexture->GetSizeY(), 1.0f / FMath::Max<int32>(1, CameraColorTexture->GetSizeX()), 1.0f / FMath::Max<int32>(1, CameraColorTexture->GetSizeY()))); + } + } + uint32 Pitch = rowPitch; + uint32 DataSize = frameSize.h * Pitch; + uint8* SrcData = (uint8*)FMemory::Malloc(DataSize); + FMemory::Memcpy(SrcData, frameData, DataSize); + + struct FUploadCameraTextureContext + { + uint8* CameraBuffer; // Render thread assumes ownership + uint32 CameraBufferPitch; + FTexture2DResource* DestTextureResource; + uint32 FrameWidth; + uint32 FrameHeight; + } Context = + { + SrcData, + Pitch, + (FTexture2DResource*)CameraColorTexture->GetResource(), + (uint32) frameSize.w, + (uint32) frameSize.h + }; + + ENQUEUE_RENDER_COMMAND(UpdateCameraColorTexture)( + [Context](FRHICommandListImmediate& RHICmdList) + { + const FUpdateTextureRegion2D UpdateRegion( + 0, 0, // Dest X, Y + 0, 0, // Source X, Y + Context.FrameWidth, // Width + Context.FrameHeight // Height + ); + + RHIUpdateTexture2D( + Context.DestTextureResource->GetTexture2DRHI(), // Destination GPU texture + 0, // Mip map index + UpdateRegion, // Update region + Context.CameraBufferPitch, // Source buffer pitch + Context.CameraBuffer); // Source buffer pointer + + FMemory::Free(Context.CameraBuffer); + } + ); +} + +void AOculusXRMR_CastingCameraActor::Execute_BindToTrackedCameraIndexIfAvailable() +{ + if (!MRState->BindToTrackedCameraIndexRequested) + { + return; + } + + FOculusXRTrackedCamera TempTrackedCamera; + if (MRSettings->GetBindToTrackedCameraIndex() >= 0) + { + TArray<FOculusXRTrackedCamera> TrackedCameras; + UOculusXRMRFunctionLibrary::GetAllTrackedCamera(TrackedCameras); + int i; + for (i = 0; i < TrackedCameras.Num(); ++i) + { + if (TrackedCameras[i].Index == MRSettings->GetBindToTrackedCameraIndex()) + { + TempTrackedCamera = TrackedCameras[i]; + break; + } + } + if (i == TrackedCameras.Num()) + { + UE_LOG(LogMR, Warning, TEXT("Unable to find TrackedCamera at index %d, use TempTrackedCamera"), MRSettings->GetBindToTrackedCameraIndex()); + } + } + else + { + UE_LOG(LogMR, Warning, TEXT("BindToTrackedCameraIndex == %d, use TempTrackedCamera"), MRSettings->GetBindToTrackedCameraIndex()); + } + + MRState->TrackedCamera = TempTrackedCamera; + if (MRState->TrackedCamera.Index < 0) + { + SetTrackedCameraUserPoseWithCameraTransform(); + } + + MRState->BindToTrackedCameraIndexRequested = false; +} + +void AOculusXRMR_CastingCameraActor::RequestTrackedCameraCalibration() +{ + TrackedCameraCalibrationRequired = true; +} + +void AOculusXRMR_CastingCameraActor::CalibrateTrackedCameraPose() +{ + SetTrackedCameraInitialPoseWithPlayerTransform(); + HasTrackedCameraCalibrationCalibrated = true; + TrackedCameraCalibrationRequired = false; +} + +void AOculusXRMR_CastingCameraActor::SetTrackedCameraInitialPoseWithPlayerTransform() +{ + using namespace OculusXRHMD; + + FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr; + if (!OculusXRHMD) + { + UE_LOG(LogMR, Warning, TEXT("Unable to retrieve OculusXRHMD")); + return; + } + + FPose CameraTrackedObjectPose; + if (!GetCameraTrackedObjectPoseInTrackingSpace(OculusXRHMD, MRState->TrackedCamera, CameraTrackedObjectPose)) + { + return; + } + + FPose CameraPose = CameraTrackedObjectPose * FPose(MRState->TrackedCamera.CalibratedRotation.Quaternion(), MRState->TrackedCamera.CalibratedOffset); + CameraPose = CameraPose * FPose(MRState->TrackedCamera.UserRotation.Quaternion(), MRState->TrackedCamera.UserOffset); + + FQuat TROrientation; + FVector TRLocation; + FRotator TRRotation; + if (!UOculusXRMRFunctionLibrary::GetTrackingReferenceLocationAndRotationInWorldSpace(MRState->TrackingReferenceComponent, TRLocation, TRRotation)) + { + UE_LOG(LogMR, Warning, TEXT("Could not get player position")); + return; + } + + TROrientation = TRRotation.Quaternion(); + FPose FinalPose = FPose(TROrientation, TRLocation) * CameraPose; + + InitialCameraAbsoluteOrientation = FinalPose.Orientation; + InitialCameraAbsolutePosition = FinalPose.Position; + InitialCameraRelativeOrientation = CameraPose.Orientation; + InitialCameraRelativePosition = CameraPose.Position; + + GetCaptureComponent2D()->FOVAngle = MRState->TrackedCamera.FieldOfView; + + if (ForegroundCaptureActor) + { + ForegroundCaptureActor->GetCaptureComponent2D()->FOVAngle = MRState->TrackedCamera.FieldOfView; + } +} + + +void AOculusXRMR_CastingCameraActor::SetTrackedCameraUserPoseWithCameraTransform() +{ + using namespace OculusXRHMD; + + FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr; + if (!OculusXRHMD) + { + UE_LOG(LogMR, Warning, TEXT("Unable to retrieve OculusXRHMD")); + return; + } + + FPose CameraTrackedObjectPose; + if (!GetCameraTrackedObjectPoseInTrackingSpace(OculusXRHMD, MRState->TrackedCamera, CameraTrackedObjectPose)) + { + return; + } + + FPose CameraPose = CameraTrackedObjectPose * FPose(MRState->TrackedCamera.CalibratedRotation.Quaternion(), MRState->TrackedCamera.CalibratedOffset); + + FQuat TROrientation; + FVector TRLocation; + FRotator TRRotation; + if (!UOculusXRMRFunctionLibrary::GetTrackingReferenceLocationAndRotationInWorldSpace(MRState->TrackingReferenceComponent, TRLocation, TRRotation)) + { + UE_LOG(LogMR, Warning, TEXT("Could not get player position")); + return; + } + TROrientation = TRRotation.Quaternion(); + FPose PlayerPose(TROrientation, TRLocation); + FPose CurrentCameraPose = PlayerPose * CameraPose; + + FPose ExpectedCameraPose(GetCaptureComponent2D()->GetComponentRotation().Quaternion(), GetCaptureComponent2D()->GetComponentLocation()); + FPose UserPose = CurrentCameraPose.Inverse() * ExpectedCameraPose; + + MRState->TrackedCamera.UserRotation = UserPose.Orientation.Rotator(); + MRState->TrackedCamera.UserOffset = UserPose.Position; +} + +void AOculusXRMR_CastingCameraActor::UpdateTrackedCameraPosition() +{ + check(HasTrackedCameraCalibrationCalibrated); + + using namespace OculusXRHMD; + + FOculusXRHMD* OculusXRHMD = GEngine->XRSystem.IsValid() ? (FOculusXRHMD*)(GEngine->XRSystem->GetHMDDevice()) : nullptr; + if (!OculusXRHMD) + { + UE_LOG(LogMR, Warning, TEXT("Unable to retrieve OculusXRHMD")); + return; + } + + FPose CameraTrackedObjectPose; + if (!GetCameraTrackedObjectPoseInTrackingSpace(OculusXRHMD, MRState->TrackedCamera, CameraTrackedObjectPose)) + { + return; + } + + FPose CameraTrackingSpacePose = FPose(MRState->TrackedCamera.CalibratedRotation.Quaternion(), MRState->TrackedCamera.CalibratedOffset); +#if PLATFORM_ANDROID + ovrpPosef OvrpPose; + FOculusXRHMDModule::GetPluginWrapper().GetTrackingTransformRelativePose(&OvrpPose, ovrpTrackingOrigin_Stage); + FPose StageToLocalPose; + OculusXRHMD->ConvertPose(OvrpPose, StageToLocalPose); + CameraTrackingSpacePose = StageToLocalPose * CameraTrackingSpacePose; +#endif + FPose CameraPose = CameraTrackedObjectPose * CameraTrackingSpacePose; + CameraPose = CameraPose * FPose(MRState->TrackedCamera.UserRotation.Quaternion(), MRState->TrackedCamera.UserOffset); + CameraPose.Position = CameraPose.Position * MRState->ScalingFactor; + + float Distance = 0.0f; + if (MRSettings->ClippingReference == EOculusXRMR_ClippingReference::CR_TrackingReference) + { + Distance = -FVector::DotProduct(CameraPose.Orientation.GetForwardVector().GetSafeNormal2D(), CameraPose.Position); + } + else if (MRSettings->ClippingReference == EOculusXRMR_ClippingReference::CR_Head) + { + FQuat HeadOrientation; + FVector HeadPosition; + OculusXRHMD->GetCurrentPose(IXRTrackingSystem::HMDDeviceId, HeadOrientation, HeadPosition); + FVector HeadToCamera = HeadPosition - CameraPose.Position; + Distance = FVector::DotProduct(CameraPose.Orientation.GetForwardVector().GetSafeNormal2D(), HeadToCamera); + } + else + { + checkNoEntry(); + } + ForegroundMaxDistance = FMath::Max(Distance, GMinClipZ); + if (ForegroundCaptureActor) + { + ForegroundCaptureActor->GetCaptureComponent2D()->MaxViewDistanceOverride = ForegroundMaxDistance; + } + + FPose FinalPose; + FQuat TROrientation; + FVector TRLocation; + FRotator TRRotation; + if (!UOculusXRMRFunctionLibrary::GetTrackingReferenceLocationAndRotationInWorldSpace(MRState->TrackingReferenceComponent, TRLocation, TRRotation)) + { + UE_LOG(LogMR, Warning, TEXT("Could not get player position")); + return; + } + + TROrientation = TRRotation.Quaternion(); + FinalPose = FPose(TROrientation, TRLocation) * CameraPose; + + FTransform FinalTransform(FinalPose.Orientation, FinalPose.Position); + RootComponent->SetWorldTransform(FinalTransform); + GetCaptureComponent2D()->FOVAngle = MRState->TrackedCamera.FieldOfView; + + if (ForegroundCaptureActor) + { + ForegroundCaptureActor->GetCaptureComponent2D()->FOVAngle = MRState->TrackedCamera.FieldOfView; + } +} + +void AOculusXRMR_CastingCameraActor::InitializeStates(UOculusXRMR_Settings* MRSettingsIn, UOculusXRMR_State* MRStateIn) +{ + MRSettings = MRSettingsIn; + MRState = MRStateIn; +} + +void AOculusXRMR_CastingCameraActor::SetupTrackedCamera() +{ + if (!RefreshExternalCamera()) + { + return; + } + + RequestTrackedCameraCalibration(); + + // Unset this flag before we can return + MRState->ChangeCameraStateRequested = false; + +#if PLATFORM_WINDOWS + // Set the plane mesh to the camera stream in direct composition or static background for external composition + if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::DirectComposition) + { + ovrpBool cameraOpen; + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().HasCameraDeviceOpened2(MRState->CurrentCapturingCamera, &cameraOpen)) && cameraOpen) + { + UE_LOG(LogMR, Log, TEXT("Create CameraColorTexture (1280x720)")); + CameraColorTexture = UTexture2D::CreateTransient(1280, 720); + CameraColorTexture->UpdateResource(); + CameraDepthTexture = DefaultTexture_White; + } + else + { + MRState->CurrentCapturingCamera = ovrpCameraDevice_None; + UE_LOG(LogMR, Error, TEXT("Unable to open CapturingCamera")); + return; + } + + SetupCameraFrameMaterialInstance(); + } + else if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::ExternalComposition) + { + SetupBackdropMaterialInstance(); + } + + RepositionPlaneMesh(); +#endif +} + +void AOculusXRMR_CastingCameraActor::SetupCameraFrameMaterialInstance() +{ + if (!ChromaKeyMaterialInstance && ChromaKeyMaterial) + { + ChromaKeyMaterialInstance = UMaterialInstanceDynamic::Create(ChromaKeyMaterial, this); + } + CameraFrameMaterialInstance = ChromaKeyMaterialInstance; + + PlaneMeshComponent->SetMaterial(0, CameraFrameMaterialInstance); + + if (CameraFrameMaterialInstance && CameraColorTexture) + { + CameraFrameMaterialInstance->SetTextureParameterValue(FName(TEXT("CameraCaptureTexture")), CameraColorTexture); + CameraFrameMaterialInstance->SetVectorParameterValue(FName(TEXT("CameraCaptureTextureSize")), + FLinearColor((float)CameraColorTexture->GetSizeX(), (float)CameraColorTexture->GetSizeY(), 1.0f / FMath::Max<int32>(1, CameraColorTexture->GetSizeX()), 1.0f / FMath::Max<int32>(1, CameraColorTexture->GetSizeY()))); + } +} + +void AOculusXRMR_CastingCameraActor::SetBackdropMaterialColor() +{ + if (BackdropMaterialInstance) + { + BackdropMaterialInstance->SetVectorParameterValue(FName(TEXT("Color")), GetForegroundLayerBackgroundColor()); + } +} + +void AOculusXRMR_CastingCameraActor::SetupBackdropMaterialInstance() +{ + if (!BackdropMaterialInstance && OpaqueColoredMaterial) + { + BackdropMaterialInstance = UMaterialInstanceDynamic::Create(OpaqueColoredMaterial, this); + BackdropMaterialInstance->SetScalarParameterValue(FName("Opacity"), 0.0f); + } + PlaneMeshComponent->SetMaterial(0, BackdropMaterialInstance); + SetBackdropMaterialColor(); +} + +void AOculusXRMR_CastingCameraActor::RepositionPlaneMesh() +{ + FVector PlaneCenter = FVector::ForwardVector * ForegroundMaxDistance; + FVector PlaneUp = FVector::UpVector; + FVector PlaneNormal = -FVector::ForwardVector; + int ViewWidth = MRSettings->bUseTrackedCameraResolution ? MRState->TrackedCamera.SizeX : MRSettings->WidthPerView; + int ViewHeight = MRSettings->bUseTrackedCameraResolution ? MRState->TrackedCamera.SizeY : MRSettings->HeightPerView; + float Width = ForegroundMaxDistance * FMath::Tan(FMath::DegreesToRadians(GetCaptureComponent2D()->FOVAngle) * 0.5f) * 2.0f; + float Height = Width * ViewHeight / ViewWidth; + FVector2D PlaneSize = FVector2D(Width, Height); + PlaneMeshComponent->Place(PlaneCenter, PlaneUp, PlaneNormal, PlaneSize); + PlaneMeshComponent->ResetRelativeTransform(); + PlaneMeshComponent->SetVisibility(true); +} + +void AOculusXRMR_CastingCameraActor::OnHMDRecentered() +{ +#if PLATFORM_WINDOWS + RefreshBoundaryMesh(); +#endif + RequestTrackedCameraCalibration(); +} + +void AOculusXRMR_CastingCameraActor::RefreshBoundaryMesh() +{ + RefreshBoundaryMeshCounter = 3; +} + +void BuildProjectionMatrix(float YMultiplier, float FOV, float FarClipPlane, FMatrix& ProjectionMatrix) +{ + if (FarClipPlane < GNearClippingPlane) + { + FarClipPlane = GNearClippingPlane; + } + + if ((int32)ERHIZBuffer::IsInverted) + { + ProjectionMatrix = FReversedZPerspectiveMatrix( + FOV, + FOV, + 1.0f, + YMultiplier, + GNearClippingPlane, + FarClipPlane + ); + } + else + { + ProjectionMatrix = FPerspectiveMatrix( + FOV, + FOV, + 1.0f, + YMultiplier, + GNearClippingPlane, + FarClipPlane + ); + } +} + +void AOculusXRMR_CastingCameraActor::UpdateRenderTargetSize() +{ + int ViewWidth = MRSettings->bUseTrackedCameraResolution ? MRState->TrackedCamera.SizeX : MRSettings->WidthPerView; + int ViewHeight = MRSettings->bUseTrackedCameraResolution ? MRState->TrackedCamera.SizeY : MRSettings->HeightPerView; + +#if PLATFORM_WINDOWS + BackgroundRenderTargets[0]->ResizeTarget(ViewWidth, ViewHeight); + if (ForegroundRenderTargets[0]) + { + ForegroundRenderTargets[0]->ResizeTarget(ViewWidth, ViewHeight); + } +#endif +#if PLATFORM_ANDROID + FIntPoint CameraTargetSize = FIntPoint(ViewWidth, ViewHeight); + float FOV = GetCaptureComponent2D()->FOVAngle * (float)PI / 360.0f; + + if (OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().Media_GetMrcFrameSize(&ViewWidth, &ViewHeight))) + { + // Frame size is doublewide, so divide by 2 + ViewWidth /= 2; + + for (unsigned int i = 0; i < NumRTs; ++i) + { + BackgroundRenderTargets[i]->ResizeTarget(ViewWidth, ViewHeight); + if (ForegroundRenderTargets[i]) + { + ForegroundRenderTargets[i]->ResizeTarget(ViewWidth, ViewHeight); + } + } + + // Use custom projection matrix for far clip plane and to use camera aspect ratio instead of rendertarget aspect ratio + float YMultiplier = (float)CameraTargetSize.X / (float)CameraTargetSize.Y; + GetCaptureComponent2D()->bUseCustomProjectionMatrix = true; + BuildProjectionMatrix(YMultiplier, FOV, GNearClippingPlane, GetCaptureComponent2D()->CustomProjectionMatrix); + if (ForegroundCaptureActor) + { + ForegroundCaptureActor->GetCaptureComponent2D()->bUseCustomProjectionMatrix = true; + BuildProjectionMatrix(YMultiplier, FOV, ForegroundMaxDistance, ForegroundCaptureActor->GetCaptureComponent2D()->CustomProjectionMatrix); + } + } +#endif +} + +void AOculusXRMR_CastingCameraActor::SetupMRCScreen() +{ +#if PLATFORM_WINDOWS + OculusXRHMD::FSpectatorScreenController* SpecScreen = nullptr; + IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr; + if (HMD) + { + SpecScreen = (OculusXRHMD::FSpectatorScreenController*)HMD->GetSpectatorScreenController(); + } + if (SpecScreen) { +#endif + UpdateRenderTargetSize(); + + // LDR for gamma correction and post process + GetCaptureComponent2D()->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR; + + // Render scene capture 2D output to spectator screen + GetCaptureComponent2D()->TextureTarget = BackgroundRenderTargets[0]; + +#if PLATFORM_WINDOWS + if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::ExternalComposition) +#endif + { + ForegroundCaptureActor = GetWorld()->SpawnActor<ASceneCapture2D>(); + + // LDR for gamma correction and post process + ForegroundCaptureActor->GetCaptureComponent2D()->CaptureSource = ESceneCaptureSource::SCS_FinalColorLDR; +#if PLATFORM_ANDROID + // Start with foreground capture actor off on android + ForegroundCaptureActor->GetCaptureComponent2D()->SetVisibility(false); +#endif + + // Don't render anything past the foreground for performance + ForegroundCaptureActor->GetCaptureComponent2D()->MaxViewDistanceOverride = ForegroundMaxDistance; + + ForegroundCaptureActor->GetCaptureComponent2D()->TextureTarget = ForegroundRenderTargets[0]; +#if PLATFORM_WINDOWS + // Render use split foreground/background rendering to spectator screen + SpecScreen->SetMRForeground(ForegroundRenderTargets[0]); + SpecScreen->SetMRBackground(BackgroundRenderTargets[0]); + SpecScreen->SetMRSpectatorScreenMode(OculusXRHMD::EMRSpectatorScreenMode::ExternalComposition); + + // Set the plane mesh to only render to foreground target + PlaneMeshComponent->SetPlaneRenderTarget(ForegroundRenderTargets[0]); +#endif + // Set foreground capture to match background capture + ForegroundCaptureActor->AttachToActor(this, FAttachmentTransformRules(EAttachmentRule::SnapToTarget, true)); + } +#if PLATFORM_WINDOWS + else if (MRSettings->GetCompositionMethod() == EOculusXRMR_CompositionMethod::DirectComposition) + { + SpecScreen->SetMRBackground(BackgroundRenderTargets[0]); + SpecScreen->SetMRSpectatorScreenMode(OculusXRHMD::EMRSpectatorScreenMode::DirectComposition); + // Set the plane mesh to only render to MRC capture target + PlaneMeshComponent->SetPlaneRenderTarget(BackgroundRenderTargets[0]); + + if (ForegroundCaptureActor) + { + ForegroundCaptureActor->Destroy(); + ForegroundCaptureActor = nullptr; + } + } + } + else + { + UE_LOG(LogMR, Error, TEXT("Cannot find spectator screen")); + } +#endif +} + +void AOculusXRMR_CastingCameraActor::CloseMRCScreen() +{ +#if PLATFORM_WINDOWS + OculusXRHMD::FSpectatorScreenController* SpecScreen = nullptr; + IHeadMountedDisplay* HMD = GEngine->XRSystem.IsValid() ? GEngine->XRSystem->GetHMDDevice() : nullptr; + if (HMD) + { + SpecScreen = (OculusXRHMD::FSpectatorScreenController*)HMD->GetSpectatorScreenController(); + } + // Restore original spectator screen mode + if (SpecScreen) { + SpecScreen->SetMRSpectatorScreenMode(OculusXRHMD::EMRSpectatorScreenMode::Default); + SpecScreen->SetMRForeground(nullptr); + SpecScreen->SetMRBackground(nullptr); + } +#endif + if (ForegroundCaptureActor) + { + ForegroundCaptureActor->Destroy(); + ForegroundCaptureActor = nullptr; + } +} + +void AOculusXRMR_CastingCameraActor::CloseTrackedCamera() +{ + if (PlaneMeshComponent) { + PlaneMeshComponent->SetVisibility(false); + } + CameraFrameMaterialInstance = NULL; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_CastingCameraActor.h b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_CastingCameraActor.h new file mode 100644 index 0000000000000000000000000000000000000000..ffd125f304b5f4be7a4e968cc207b9ec9cf7a481 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_CastingCameraActor.h @@ -0,0 +1,148 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "UObject/ObjectMacros.h" +#include "Engine/SceneCapture2D.h" +#include "AudioResampler.h" +#include "AudioDefines.h" +#include "OculusXRPluginWrapper.h" +#include "Materials/MaterialInstanceDynamic.h" +#include "AudioMixer.h" +#include "OculusXRMR_CastingCameraActor.generated.h" + +#if PLATFORM_ANDROID +#define MRC_SWAPCHAIN_LENGTH 3 +#endif + +class UOculusXRMR_PlaneMeshComponent; +class UMaterial; +class AOculusXRMR_BoundaryActor; +class UTextureRenderTarget2D; +class UOculusXRMR_Settings; +class UOculusXRMR_State; + +/** +* The camera actor in the level that tracks the binded physical camera in game +*/ +UCLASS(ClassGroup = OculusXRMR, NotPlaceable, NotBlueprintable) +class AOculusXRMR_CastingCameraActor : public ASceneCapture2D +{ + GENERATED_BODY() + +public: + AOculusXRMR_CastingCameraActor(const FObjectInitializer& ObjectInitializer); + + /** Initialize the MRC settings and states */ + void InitializeStates(UOculusXRMR_Settings* MRSettingsIn, UOculusXRMR_State* MRStateIn); + + virtual void BeginPlay() override; + virtual void EndPlay(EEndPlayReason::Type Reason) override; + virtual void Tick(float DeltaTime) override; + + virtual void BeginDestroy() override; + + UPROPERTY() + class UVRNotificationsComponent* VRNotificationComponent; + + UPROPERTY() + UTexture2D* CameraColorTexture; + + UPROPERTY() + UTexture2D* CameraDepthTexture; + + UPROPERTY() + UOculusXRMR_PlaneMeshComponent* PlaneMeshComponent; + + UPROPERTY() + UMaterial* ChromaKeyMaterial; + + UPROPERTY() + UMaterial* OpaqueColoredMaterial; + + UPROPERTY() + UMaterialInstanceDynamic* ChromaKeyMaterialInstance; + + UPROPERTY() + UMaterialInstanceDynamic* CameraFrameMaterialInstance; + + UPROPERTY() + UMaterialInstanceDynamic* BackdropMaterialInstance; + + UPROPERTY() + class UTexture2D* DefaultTexture_White; + + bool TrackedCameraCalibrationRequired; + bool HasTrackedCameraCalibrationCalibrated; + FQuat InitialCameraAbsoluteOrientation; + FVector InitialCameraAbsolutePosition; + FQuat InitialCameraRelativeOrientation; + FVector InitialCameraRelativePosition; + + int32 RefreshBoundaryMeshCounter; + +private: + + /** Move the casting camera to follow the tracking reference (i.e. player) */ + void RequestTrackedCameraCalibration(); + + bool RefreshExternalCamera(); + void UpdateCameraColorTexture(const ovrpSizei &colorFrameSize, const ovrpByte* frameData, int rowPitch); + + void CalibrateTrackedCameraPose(); + void SetTrackedCameraUserPoseWithCameraTransform(); + void SetTrackedCameraInitialPoseWithPlayerTransform(); + void UpdateTrackedCameraPosition(); + + /** Initialize the tracked physical camera */ + void SetupTrackedCamera(); + + /** Close the tracked physical camera */ + void CloseTrackedCamera(); + + void OnHMDRecentered(); + + const FColor& GetForegroundLayerBackgroundColor() const { return ForegroundLayerBackgroundColor; } + + void SetupCameraFrameMaterialInstance(); + void SetBackdropMaterialColor(); + void SetupBackdropMaterialInstance(); + void RepositionPlaneMesh(); + void RefreshBoundaryMesh(); + void UpdateRenderTargetSize(); + void SetupMRCScreen(); + void CloseMRCScreen(); + + void Execute_BindToTrackedCameraIndexIfAvailable(); + + FColor ForegroundLayerBackgroundColor; + float ForegroundMaxDistance; + + UPROPERTY() + TArray<UTextureRenderTarget2D*> BackgroundRenderTargets; + + UPROPERTY() + ASceneCapture2D* ForegroundCaptureActor; + + UPROPERTY() + TArray<UTextureRenderTarget2D*> ForegroundRenderTargets; + + UPROPERTY() + TArray<double> PoseTimes; + + UPROPERTY() + UOculusXRMR_Settings* MRSettings; + + UPROPERTY() + UOculusXRMR_State* MRState; + +#if PLATFORM_ANDROID + TArray<Audio::AlignedFloatBuffer> AudioBuffers; + TArray<double> AudioTimes; + + int SyncId; + + const unsigned int NumRTs = MRC_SWAPCHAIN_LENGTH; + unsigned int RenderedRTs; + unsigned int CaptureIndex; +#endif +}; diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_PlaneMeshComponent.cpp b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_PlaneMeshComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8622730ad0dd6dbe421f32797aadb564392df1e9 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_PlaneMeshComponent.cpp @@ -0,0 +1,270 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRMR_PlaneMeshComponent.h" +#include "RenderingThread.h" +#include "RenderResource.h" +#include "PrimitiveViewRelevance.h" +#include "PrimitiveSceneProxy.h" +#include "VertexFactory.h" +#include "MaterialShared.h" +#include "Engine/CollisionProfile.h" +#include "Engine/TextureRenderTarget2D.h" +#include "Materials/Material.h" +#include "LocalVertexFactory.h" +#include "SceneManagement.h" +#include "DynamicMeshBuilder.h" +#include "EngineGlobals.h" +#include "Engine/Engine.h" + +/** Scene proxy */ +class FOculusXRMR_PlaneMeshSceneProxy : public FPrimitiveSceneProxy +{ +public: + + FOculusXRMR_PlaneMeshSceneProxy(UOculusXRMR_PlaneMeshComponent* Component, UTextureRenderTarget2D* RenderTarget) + : FPrimitiveSceneProxy(Component) + , MaterialRelevance(Component->GetMaterialRelevance(GetScene().GetFeatureLevel())) + , PlaneRenderTarget(RenderTarget) + { + const FColor VertexColor(255, 255, 255); + + const int32 NumTris = Component->CustomMeshTris.Num(); + Vertices.AddUninitialized(NumTris * 3); + Indices.AddUninitialized(NumTris * 3); + // Add each triangle to the vertex/index buffer + for (int32 TriIdx = 0; TriIdx < NumTris; TriIdx++) + { + FOculusXRMR_PlaneMeshTriangle& Tri = Component->CustomMeshTris[TriIdx]; + + const FVector Edge01 = (Tri.Vertex1 - Tri.Vertex0); + const FVector Edge02 = (Tri.Vertex2 - Tri.Vertex0); + + const FVector TangentX = Edge01.GetSafeNormal(); + const FVector TangentZ = (Edge02 ^ Edge01).GetSafeNormal(); + const FVector TangentY = (TangentX ^ TangentZ).GetSafeNormal(); + + FDynamicMeshVertex Vert; + + Vert.Color = VertexColor; + Vert.SetTangents((FVector3f)TangentX, (FVector3f)TangentY, (FVector3f)TangentZ); + + Vert.Position = (FVector3f)Tri.Vertex0; + Vert.TextureCoordinate[0] = FVector2f(Tri.UV0); // LWC_TODO: Precision loss + Vertices[TriIdx * 3 + 0] = Vert; + Indices[TriIdx * 3 + 0] = TriIdx * 3 + 0; + + Vert.Position = (FVector3f)Tri.Vertex1; + Vert.TextureCoordinate[0] = FVector2f(Tri.UV1); // LWC_TODO: Precision loss + Vertices[TriIdx * 3 + 1] = Vert; + Indices[TriIdx * 3 + 1] = TriIdx * 3 + 1; + + Vert.Position = (FVector3f)Tri.Vertex2; + Vert.TextureCoordinate[0] = FVector2f(Tri.UV2); // LWC_TODO: Precision loss + Vertices[TriIdx * 3 + 2] = Vert; + Indices[TriIdx * 3 + 2] = TriIdx * 3 + 2; + } + + // Grab material + Material = Component->GetMaterial(0); + if (Material == NULL) + { + Material = UMaterial::GetDefaultMaterial(MD_Surface); + } + } + + virtual ~FOculusXRMR_PlaneMeshSceneProxy() + { + } + + SIZE_T GetTypeHash() const override + { + static size_t UniquePointer; + return reinterpret_cast<size_t>(&UniquePointer); + } + + virtual void GetDynamicMeshElements(const TArray<const FSceneView*>& Views, const FSceneViewFamily& ViewFamily, uint32 VisibilityMap, FMeshElementCollector& Collector) const override + { + QUICK_SCOPE_CYCLE_COUNTER(STAT_OculusXRMR_PlaneMeshSceneProxy_GetDynamicMeshElements); + + // the mesh is only visible inside the CastingViewport, and the Full CastingLayer (the Composition mode) + if (PlaneRenderTarget && ViewFamily.RenderTarget == PlaneRenderTarget->GetRenderTargetResource()) + { + const bool bWireframe = AllowDebugViewmodes() && ViewFamily.EngineShowFlags.Wireframe; + + FMaterialRenderProxy* MaterialProxy = NULL; + if (bWireframe) + { + auto WireframeMaterialInstance = new FColoredMaterialRenderProxy( + GEngine->WireframeMaterial->GetRenderProxy(), + FLinearColor(0, 0.5f, 1.f) + ); + + Collector.RegisterOneFrameMaterialProxy(WireframeMaterialInstance); + MaterialProxy = WireframeMaterialInstance; + } + else + { + MaterialProxy = Material->GetRenderProxy(); + } + + for (int32 ViewIndex = 0; ViewIndex < Views.Num(); ViewIndex++) + { + if (VisibilityMap & (1 << ViewIndex)) + { + const FSceneView* View = Views[ViewIndex]; + + FDynamicMeshBuilder DynamicMeshBuilder(View->GetFeatureLevel()); + DynamicMeshBuilder.AddVertices(Vertices); + DynamicMeshBuilder.AddTriangles(Indices); + + DynamicMeshBuilder.GetMesh(GetLocalToWorld(), MaterialProxy, SDPG_World, true, false, ViewIndex, Collector); + + // -- Original draw code for reference -- + //FMeshBatch& Mesh = Collector.AllocateMesh(); + //FMeshBatchElement& BatchElement = Mesh.Elements[0]; + //BatchElement.IndexBuffer = &IndexBuffer; + //Mesh.bWireframe = bWireframe; + //Mesh.VertexFactory = &VertexFactory; + //Mesh.MaterialRenderProxy = MaterialProxy; + //BatchElement.PrimitiveUniformBuffer = CreatePrimitiveUniformBufferImmediate(GetLocalToWorld(), GetBounds(), GetLocalBounds(), true, DrawsVelocity()); + //BatchElement.FirstIndex = 0; + //BatchElement.NumPrimitives = IndexBuffer.Indices.Num() / 3; + //BatchElement.MinVertexIndex = 0; + //BatchElement.MaxVertexIndex = VertexBuffer.Vertices.Num() - 1; + //Mesh.ReverseCulling = IsLocalToWorldDeterminantNegative(); + //Mesh.Type = PT_TriangleList; + //Mesh.DepthPriorityGroup = SDPG_World; + //Mesh.bCanApplyViewModeOverrides = false; + //Collector.AddMesh(ViewIndex, Mesh); + } + } + } + } + + virtual FPrimitiveViewRelevance GetViewRelevance(const FSceneView* View) const override + { + FPrimitiveViewRelevance Result; + Result.bDrawRelevance = IsShown(View); + Result.bShadowRelevance = IsShadowCast(View); + Result.bDynamicRelevance = true; + Result.bRenderInMainPass = ShouldRenderInMainPass(); + Result.bUsesLightingChannels = GetLightingChannelMask() != GetDefaultLightingChannelMask(); + Result.bRenderCustomDepth = ShouldRenderCustomDepth(); + MaterialRelevance.SetPrimitiveViewRelevance(Result); + return Result; + } + + virtual bool CanBeOccluded() const override + { + return !MaterialRelevance.bDisableDepthTest; + } + + virtual uint32 GetMemoryFootprint(void) const override { return(sizeof(*this) + GetAllocatedSize()); } + + uint32 GetAllocatedSize(void) const { return(FPrimitiveSceneProxy::GetAllocatedSize()); } + +private: + UMaterialInterface* Material; + TArray<FDynamicMeshVertex> Vertices; + TArray<uint32> Indices; + FMaterialRelevance MaterialRelevance; + UTextureRenderTarget2D* PlaneRenderTarget; +}; + +////////////////////////////////////////////////////////////////////////// + +UOculusXRMR_PlaneMeshComponent::UOculusXRMR_PlaneMeshComponent(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + PrimaryComponentTick.bCanEverTick = false; + + SetCollisionProfileName(UCollisionProfile::BlockAllDynamic_ProfileName); + + bRenderCustomDepth = true; +} + +bool UOculusXRMR_PlaneMeshComponent::SetCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles) +{ + CustomMeshTris = Triangles; + + // Need to recreate scene proxy to send it over + MarkRenderStateDirty(); + + return true; +} + +void UOculusXRMR_PlaneMeshComponent::AddCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles) +{ + CustomMeshTris.Append(Triangles); + + // Need to recreate scene proxy to send it over + MarkRenderStateDirty(); +} + +void UOculusXRMR_PlaneMeshComponent::ClearCustomMeshTriangles() +{ + CustomMeshTris.Reset(); + + // Need to recreate scene proxy to send it over + MarkRenderStateDirty(); +} + +void UOculusXRMR_PlaneMeshComponent::Place(const FVector& Center, const FVector& Up, const FVector& Normal, const FVector2D& Size) +{ + FVector Right = FVector::CrossProduct(Up, Normal); + + FVector Up_N = Up.GetUnsafeNormal(); + FVector Right_N = Right.GetUnsafeNormal(); + + FVector V0 = Center - Right_N * Size.X * 0.5f - Up_N * Size.Y * 0.5f; + FVector2D UV0(1, 1); + FVector V1 = Center + Right_N * Size.X * 0.5f - Up_N * Size.Y * 0.5f; + FVector2D UV1(0, 1); + FVector V2 = Center - Right_N * Size.X * 0.5f + Up_N * Size.Y * 0.5f; + FVector2D UV2(1, 0); + FVector V3 = Center + Right_N * Size.X * 0.5f + Up_N * Size.Y * 0.5f; + FVector2D UV3(0, 0); + + FOculusXRMR_PlaneMeshTriangle Tri0, Tri1; + Tri0.Vertex0 = V1; + Tri0.UV0 = UV1; + Tri0.Vertex1 = V0; + Tri0.UV1 = UV0; + Tri0.Vertex2 = V2; + Tri0.UV2 = UV2; + Tri1.Vertex0 = V1; + Tri1.UV0 = UV1; + Tri1.Vertex1 = V2; + Tri1.UV1 = UV2; + Tri1.Vertex2 = V3; + Tri1.UV2 = UV3; + + SetCustomMeshTriangles({ Tri0, Tri1 }); +} + + +FPrimitiveSceneProxy* UOculusXRMR_PlaneMeshComponent::CreateSceneProxy() +{ + FPrimitiveSceneProxy* Proxy = NULL; + if (CustomMeshTris.Num() > 0) + { + Proxy = new FOculusXRMR_PlaneMeshSceneProxy(this, PlaneRenderTarget); + } + return Proxy; +} + +int32 UOculusXRMR_PlaneMeshComponent::GetNumMaterials() const +{ + return 1; +} + + +FBoxSphereBounds UOculusXRMR_PlaneMeshComponent::CalcBounds(const FTransform& LocalToWorld) const +{ + FBoxSphereBounds NewBounds; + NewBounds.Origin = FVector::ZeroVector; + NewBounds.BoxExtent = FVector(HALF_WORLD_MAX, HALF_WORLD_MAX, HALF_WORLD_MAX); + NewBounds.SphereRadius = FMath::Sqrt(3.0f * FMath::Square(HALF_WORLD_MAX)); + return NewBounds; +} + diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_PlaneMeshComponent.h b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_PlaneMeshComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..02a805d945fb446f401fb7e2556395f98adf4d56 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_PlaneMeshComponent.h @@ -0,0 +1,79 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once + +#include "UObject/ObjectMacros.h" +#include "Components/MeshComponent.h" +#include "OculusXRMR_PlaneMeshComponent.generated.h" + +class FPrimitiveSceneProxy; +class UTextureRenderTarget2D; + +USTRUCT(BlueprintType) +struct FOculusXRMR_PlaneMeshTriangle +{ + GENERATED_USTRUCT_BODY() + + UPROPERTY() + FVector Vertex0 = FVector(0.0f); + + UPROPERTY() + FVector2D UV0 = FVector2D(0.0f); + + UPROPERTY() + FVector Vertex1 = FVector(0.0f); + + UPROPERTY() + FVector2D UV1 = FVector2D(0.0f); + + UPROPERTY() + FVector Vertex2 = FVector(0.0f); + + UPROPERTY() + FVector2D UV2 = FVector2D(0.0f); +}; + +/** Component that allows you to specify custom triangle mesh geometry */ +UCLASS(hidecategories = (Object, LOD, Physics, Collision), editinlinenew, ClassGroup = Rendering, NotPlaceable, NotBlueprintable) +class UOculusXRMR_PlaneMeshComponent: public UMeshComponent +{ + GENERATED_UCLASS_BODY() + + /** Set the geometry to use on this triangle mesh */ + UFUNCTION(BlueprintCallable, Category = "Components|CustomMesh") + bool SetCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles); + + /** Add to the geometry to use on this triangle mesh. This may cause an allocation. Use SetCustomMeshTriangles() instead when possible to reduce allocations. */ + UFUNCTION(BlueprintCallable, Category = "Components|CustomMesh") + void AddCustomMeshTriangles(const TArray<FOculusXRMR_PlaneMeshTriangle>& Triangles); + + /** Removes all geometry from this triangle mesh. Does not deallocate memory, allowing new geometry to reuse the existing allocation. */ + UFUNCTION(BlueprintCallable, Category = "Components|CustomMesh") + void ClearCustomMeshTriangles(); + + void Place(const FVector& Center, const FVector& Up, const FVector& Normal, const FVector2D& Size); + + void SetPlaneRenderTarget(UTextureRenderTarget2D* RT) { PlaneRenderTarget = RT; } + +private: + + //~ Begin UPrimitiveComponent Interface. + virtual FPrimitiveSceneProxy* CreateSceneProxy() override; + //~ End UPrimitiveComponent Interface. + + //~ Begin UMeshComponent Interface. + virtual int32 GetNumMaterials() const override; + //~ End UMeshComponent Interface. + + //~ Begin USceneComponent Interface. + virtual FBoxSphereBounds CalcBounds(const FTransform& LocalToWorld) const override; + //~ Begin USceneComponent Interface. + + TArray<FOculusXRMR_PlaneMeshTriangle> CustomMeshTris; + + UTextureRenderTarget2D* PlaneRenderTarget; + + friend class FOculusXRMR_PlaneMeshSceneProxy; +}; + + diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_Settings.cpp b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_Settings.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f73bd20f99dc53a910e7f30c2716ae9a05c5b95e --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_Settings.cpp @@ -0,0 +1,183 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRMR_Settings.h" +#include "OculusXRMRPrivate.h" +#include "OculusXRHMD.h" +#include "Engine/Engine.h" + +UOculusXRMR_Settings::UOculusXRMR_Settings(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) + , ClippingReference(EOculusXRMR_ClippingReference::CR_Head) + , bUseTrackedCameraResolution(true) + , WidthPerView(960) + , HeightPerView(540) + , CastingLatency(0.0f) + , BackdropColor(FColor::Green) + , HandPoseStateLatency(0.0f) + , ChromaKeyColor(FColor::Green) + , ChromaKeySimilarity(0.6f) + , ChromaKeySmoothRange(0.03f) + , ChromaKeySpillRange(0.04f) + , ExternalCompositionPostProcessEffects(EOculusXRMR_PostProcessEffects::PPE_Off) + , bIsCasting(false) + , CompositionMethod(EOculusXRMR_CompositionMethod::ExternalComposition) + , CapturingCamera(EOculusXRMR_CameraDeviceEnum::CD_WebCamera0) + , BindToTrackedCameraIndex(-1) +{ +} + +void UOculusXRMR_Settings::SetCompositionMethod(EOculusXRMR_CompositionMethod val) +{ + if (CompositionMethod == val) + { + return; + } + auto old = CompositionMethod; + CompositionMethod = val; + CompositionMethodChangeDelegate.Execute(old, val); +} + +void UOculusXRMR_Settings::SetCapturingCamera(EOculusXRMR_CameraDeviceEnum val) +{ + if (CapturingCamera == val) + { + return; + } + auto old = CapturingCamera; + CapturingCamera = val; + CapturingCameraChangeDelegate.Execute(old, val); +} + +void UOculusXRMR_Settings::SetIsCasting(bool val) +{ + if (bIsCasting == val) + { + return; + } + auto old = bIsCasting; + bIsCasting = val; + IsCastingChangeDelegate.Execute(old, val); +} + +void UOculusXRMR_Settings::BindToTrackedCameraIndexIfAvailable(int InTrackedCameraIndex) +{ + if (BindToTrackedCameraIndex == InTrackedCameraIndex) + { + return; + } + auto old = BindToTrackedCameraIndex; + BindToTrackedCameraIndex = InTrackedCameraIndex; + TrackedCameraIndexChangeDelegate.Execute(old, InTrackedCameraIndex); +} + +void UOculusXRMR_Settings::LoadFromIni() +{ + if (!GConfig) + { + UE_LOG(LogMR, Warning, TEXT("GConfig is NULL")); + return; + } + + // Flushing the GEngineIni is necessary to get the settings reloaded at the runtime, but the manual flushing + // could cause an assert when loading audio settings if launching through editor at the 2nd time. Disabled temporarily. + //GConfig->Flush(true, GEngineIni); + + const TCHAR* OculusXRMRSettings = TEXT("Oculus.Settings.MixedReality"); + bool v; + float f; + int32 i; + FVector vec; + FColor color; + if (GConfig->GetInt(OculusXRMRSettings, TEXT("CompositionMethod"), i, GEngineIni)) + { + SetCompositionMethod((EOculusXRMR_CompositionMethod)i); + } + if (GConfig->GetInt(OculusXRMRSettings, TEXT("ClippingReference"), i, GEngineIni)) + { + ClippingReference = (EOculusXRMR_ClippingReference)i; + } + if (GConfig->GetBool(OculusXRMRSettings, TEXT("bUseTrackedCameraResolution"), v, GEngineIni)) + { + bUseTrackedCameraResolution = v; + } + if (GConfig->GetInt(OculusXRMRSettings, TEXT("WidthPerView"), i, GEngineIni)) + { + WidthPerView = i; + } + if (GConfig->GetInt(OculusXRMRSettings, TEXT("HeightPerView"), i, GEngineIni)) + { + HeightPerView = i; + } + if (GConfig->GetInt(OculusXRMRSettings, TEXT("CapturingCamera"), i, GEngineIni)) + { + CapturingCamera = (EOculusXRMR_CameraDeviceEnum)i; + } + if (GConfig->GetFloat(OculusXRMRSettings, TEXT("CastingLatency"), f, GEngineIni)) + { + CastingLatency = f; + } + if (GConfig->GetColor(OculusXRMRSettings, TEXT("BackdropColor"), color, GEngineIni)) + { + BackdropColor = color; + } + if (GConfig->GetFloat(OculusXRMRSettings, TEXT("HandPoseStateLatency"), f, GEngineIni)) + { + HandPoseStateLatency = f; + } + if (GConfig->GetColor(OculusXRMRSettings, TEXT("ChromaKeyColor"), color, GEngineIni)) + { + ChromaKeyColor = color; + } + if (GConfig->GetFloat(OculusXRMRSettings, TEXT("ChromaKeySimilarity"), f, GEngineIni)) + { + ChromaKeySimilarity = f; + } + if (GConfig->GetFloat(OculusXRMRSettings, TEXT("ChromaKeySmoothRange"), f, GEngineIni)) + { + ChromaKeySmoothRange = f; + } + if (GConfig->GetFloat(OculusXRMRSettings, TEXT("ChromaKeySpillRange"), f, GEngineIni)) + { + ChromaKeySpillRange = f; + } + if (GConfig->GetInt(OculusXRMRSettings, TEXT("BindToTrackedCameraIndex"), i, GEngineIni)) + { + BindToTrackedCameraIndexIfAvailable(i); + } + if (GConfig->GetInt(OculusXRMRSettings, TEXT("ExternalCompositionPostProcessEffects"), i, GEngineIni)) + { + ExternalCompositionPostProcessEffects = (EOculusXRMR_PostProcessEffects)i; + } + + UE_LOG(LogMR, Log, TEXT("MixedReality settings loaded from Engine.ini")); +} + +void UOculusXRMR_Settings::SaveToIni() const +{ + if (!GConfig) + { + UE_LOG(LogMR, Warning, TEXT("GConfig is NULL")); + return; + } + + const TCHAR* OculusXRMRSettings = TEXT("Oculus.Settings.MixedReality"); + GConfig->SetInt(OculusXRMRSettings, TEXT("CompositionMethod"), (int32)CompositionMethod, GEngineIni); + GConfig->SetInt(OculusXRMRSettings, TEXT("ClippingReference"), (int32)ClippingReference, GEngineIni); + GConfig->SetBool(OculusXRMRSettings, TEXT("bUseTrackedCameraResolution"), bUseTrackedCameraResolution, GEngineIni); + GConfig->SetInt(OculusXRMRSettings, TEXT("WidthPerView"), WidthPerView, GEngineIni); + GConfig->SetInt(OculusXRMRSettings, TEXT("HeightPerView"), HeightPerView, GEngineIni); + GConfig->SetInt(OculusXRMRSettings, TEXT("CapturingCamera"), (int32)CapturingCamera, GEngineIni); + GConfig->SetFloat(OculusXRMRSettings, TEXT("CastingLatency"), CastingLatency, GEngineIni); + GConfig->SetColor(OculusXRMRSettings, TEXT("BackdropColor"), BackdropColor, GEngineIni); + GConfig->SetFloat(OculusXRMRSettings, TEXT("HandPoseStateLatency"), HandPoseStateLatency, GEngineIni); + GConfig->SetColor(OculusXRMRSettings, TEXT("ChromaKeyColor"), ChromaKeyColor, GEngineIni); + GConfig->SetFloat(OculusXRMRSettings, TEXT("ChromaKeySimilarity"), ChromaKeySimilarity, GEngineIni); + GConfig->SetFloat(OculusXRMRSettings, TEXT("ChromaKeySmoothRange"), ChromaKeySmoothRange, GEngineIni); + GConfig->SetFloat(OculusXRMRSettings, TEXT("ChromaKeySpillRange"), ChromaKeySpillRange, GEngineIni); + GConfig->SetInt(OculusXRMRSettings, TEXT("BindToTrackedCameraIndex"), (int32)BindToTrackedCameraIndex, GEngineIni); + GConfig->SetInt(OculusXRMRSettings, TEXT("ExternalCompositionPostProcessEffects"), (int32)ExternalCompositionPostProcessEffects, GEngineIni); + + GConfig->Flush(false, GEngineIni); + + UE_LOG(LogMR, Log, TEXT("MixedReality settings saved to Engine.ini")); +} diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_State.cpp b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_State.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9d42bac510065c0e9490835fd6f7f26af1fc295c --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_State.cpp @@ -0,0 +1,13 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#include "OculusXRMR_State.h" +#include "OculusXRMRFunctionLibrary.h" + +UOculusXRMR_State::UOculusXRMR_State(const FObjectInitializer& ObjectInitializer) + : TrackedCamera() + , TrackingReferenceComponent(nullptr) + , ScalingFactor(1.0f) + , CurrentCapturingCamera(ovrpCameraDevice_None) + , ChangeCameraStateRequested(false) + , BindToTrackedCameraIndexRequested(false) +{ +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_State.h b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_State.h new file mode 100644 index 0000000000000000000000000000000000000000..402d3ec56e8e63655c52805c28c96c78da93926f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Private/OculusXRMR_State.h @@ -0,0 +1,118 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "UObject/ObjectMacros.h" +#include "OculusXRFunctionLibrary.h" +#include "OculusXRPluginWrapper.h" + +#include "OculusXRMR_State.generated.h" + +USTRUCT() +struct FOculusXRTrackedCamera +{ + GENERATED_USTRUCT_BODY() + + /** >=0: the index of the external camera + * -1: not bind to any external camera (and would be setup to match the manual CastingCameraActor placement) + */ + UPROPERTY() + int32 Index; + + /** The external camera name set through the CameraTool */ + UPROPERTY() + FString Name; + + /** The time that this camera was updated */ + UPROPERTY() + double UpdateTime; + + /** The horizontal FOV, in degrees */ + UPROPERTY(meta = (UIMin = "5.0", UIMax = "170", ClampMin = "0.001", ClampMax = "360.0", Units = deg)) + float FieldOfView; + + /** The resolution of the camera frame */ + UPROPERTY() + int32 SizeX; + + /** The resolution of the camera frame */ + UPROPERTY() + int32 SizeY; + + /** The tracking node the external camera is bound to */ + UPROPERTY() + EOculusXRTrackedDeviceType AttachedTrackedDevice; + + /** The relative pose of the camera to the attached tracking device */ + UPROPERTY() + FRotator CalibratedRotation; + + /** The relative pose of the camera to the attached tracking device */ + UPROPERTY() + FVector CalibratedOffset; + + /** (optional) The user pose is provided to fine tuning the relative camera pose at the run-time */ + UPROPERTY() + FRotator UserRotation; + + /** (optional) The user pose is provided to fine tuning the relative camera pose at the run-time */ + UPROPERTY() + FVector UserOffset; + + /** The raw pose of the camera to the attached tracking device (Deprecated) */ + UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "All camera pose info is now in stage space, do not use raw pose data.")) + FRotator RawRotation_DEPRECATED; + + /** The raw pose of the camera to the attached tracking device (Deprecated) */ + UPROPERTY(meta = (DeprecatedProperty, DeprecationMessage = "All camera pose info is now in stage space, do not use raw pose data.")) + FVector RawOffset_DEPRECATED; + + FOculusXRTrackedCamera() + : Index(-1) + , Name(TEXT("Unknown")) + , UpdateTime(0.0f) + , FieldOfView(90.0f) + , SizeX(1280) + , SizeY(720) + , AttachedTrackedDevice(EOculusXRTrackedDeviceType::None) + , CalibratedRotation(EForceInit::ForceInitToZero) + , CalibratedOffset(EForceInit::ForceInitToZero) + , UserRotation(EForceInit::ForceInitToZero) + , UserOffset(EForceInit::ForceInitToZero) + , RawRotation_DEPRECATED(EForceInit::ForceInitToZero) + , RawOffset_DEPRECATED(EForceInit::ForceInitToZero) + {} +}; + +/** +* Object to hold the state of MR capture and capturing camera +*/ +UCLASS(ClassGroup = OculusXRMR, NotPlaceable, NotBlueprintable) +class UOculusXRMR_State : public UObject +{ + GENERATED_BODY() + +public: + + UOculusXRMR_State(const FObjectInitializer& ObjectInitializer); + + UPROPERTY() + FOculusXRTrackedCamera TrackedCamera; + + // Component at the tracking origin that the camera calibration is applied to + UPROPERTY() + class USceneComponent* TrackingReferenceComponent; + + // A multiplier on the camera distance, should be based on the scaling of the player component + UPROPERTY() + double ScalingFactor; + + ovrpCameraDevice CurrentCapturingCamera; + + /** Flag indicating a change in the tracked camera state for the camera actor to consume */ + UPROPERTY() + bool ChangeCameraStateRequested; + + /** Flag indicating a change in the tracked camera index for the camera actor to consume */ + UPROPERTY() + bool BindToTrackedCameraIndexRequested; +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRMR/Public/IOculusXRMRModule.h b/Plugins/OculusXR/Source/OculusXRMR/Public/IOculusXRMRModule.h new file mode 100644 index 0000000000000000000000000000000000000000..6f1b4daf34038cfea98d261c00a4fa5ec467e833 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Public/IOculusXRMRModule.h @@ -0,0 +1,38 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Modules/ModuleManager.h" + +// Oculus support is not available on Windows XP +#define OCULUS_MR_SUPPORTED_PLATFORMS ((PLATFORM_WINDOWS && WINVER > 0x0502) || PLATFORM_ANDROID) + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IOculusXRMRModule : 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 IOculusXRMRModule& Get() + { + return FModuleManager::GetModuleChecked< IOculusXRMRModule >( "OculusXRMR" ); + } + + /** + * 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( "OculusXRMR" ); + } +}; diff --git a/Plugins/OculusXR/Source/OculusXRMR/Public/OculusXRMRFunctionLibrary.h b/Plugins/OculusXR/Source/OculusXRMR/Public/OculusXRMRFunctionLibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..84c9819e7706d3e73a04b2e78d7d1acea0d36516 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Public/OculusXRMRFunctionLibrary.h @@ -0,0 +1,59 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "UObject/ObjectMacros.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "OculusXRMRFunctionLibrary.generated.h" + +class USceneComponent; +class UOculusXRMR_Settings; +struct FOculusXRTrackedCamera; + +namespace OculusXRHMD +{ + class FOculusXRHMD; +} + +UCLASS() +class OCULUSXRMR_API UOculusXRMRFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_UCLASS_BODY() + +public: + // Get the OculusXRMR settings object + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR", meta = (DisplayName = "Get Oculus MR Settings")) + static UOculusXRMR_Settings* GetOculusXRMRSettings(); + + // Get the component that the OculusXRMR camera is tracking. When this is null, the camera will track the player pawn. + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR") + static USceneComponent* GetTrackingReferenceComponent(); + + // Set the component for the OculusXRMR camera to track. If this is set to null, the camera will track the player pawn. + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR") + static bool SetTrackingReferenceComponent(USceneComponent* Component); + + // Get the scaling factor for the MRC configuration. Returns 0 if not available. + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR", meta = (DisplayName = "Get MRC Scaling Factor")) + static float GetMrcScalingFactor(); + + // Set the scaling factor for the MRC configuration. This should be a positive value set to the same scaling as the VR player pawn so that the game capture and camera video are aligned. + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR", meta = (DisplayName = "Set MRC Scaling Factor")) + static bool SetMrcScalingFactor(float ScalingFactor = 1.0f); + + // Check if MRC is enabled + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR") + static bool IsMrcEnabled(); + + // Check if MRC is enabled and actively capturing + UFUNCTION(BlueprintCallable, Category = "OculusLibrary|MR") + static bool IsMrcActive(); + +public: + + static class OculusXRHMD::FOculusXRHMD* GetOculusXRHMD(); + + /** Retrieve an array of all (calibrated) tracked cameras which were calibrated through the CameraTool */ + static void GetAllTrackedCamera(TArray<FOculusXRTrackedCamera>& TrackedCameras, bool bCalibratedOnly = true); + + static bool GetTrackingReferenceLocationAndRotationInWorldSpace(USceneComponent* TrackingReferenceComponent, FVector& TRLocation, FRotator& TRRotation); +}; diff --git a/Plugins/OculusXR/Source/OculusXRMR/Public/OculusXRMR_Settings.h b/Plugins/OculusXR/Source/OculusXRMR/Public/OculusXRMR_Settings.h new file mode 100644 index 0000000000000000000000000000000000000000..5c3a9b038ad0f3cfb8c174bd6b0c60820e292bc9 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMR/Public/OculusXRMR_Settings.h @@ -0,0 +1,177 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "UObject/ObjectMacros.h" + +#include "OculusXRMR_Settings.generated.h" + +UENUM(BlueprintType) +enum class EOculusXRMR_CameraDeviceEnum : uint8 +{ + CD_None UMETA(DisplayName = "None"), + CD_WebCamera0 UMETA(DisplayName = "Web Camera 0"), + CD_WebCamera1 UMETA(DisplayName = "Web Camera 1"), +}; + +UENUM(BlueprintType) +enum class EOculusXRMR_ClippingReference : uint8 +{ + CR_TrackingReference UMETA(DisplayName = "Tracking Reference"), + CR_Head UMETA(DisplayName = "Head"), +}; + +UENUM(BlueprintType) +enum class EOculusXRMR_PostProcessEffects : uint8 +{ + PPE_Off UMETA(DisplayName = "Off"), + PPE_On UMETA(DisplayName = "On"), +}; + +UENUM(BlueprintType) +enum class EOculusXRMR_CompositionMethod : uint8 +{ + /* Generate both foreground and background views for compositing with 3rd-party software like OBS. */ + ExternalComposition UMETA(DisplayName = "External Composition"), + /* Composite the camera stream directly to the output with the proper depth.*/ + DirectComposition UMETA(DisplayName = "Direct Composition") +}; + +UCLASS(ClassGroup = OculusXRMR, Blueprintable) +class UOculusXRMR_Settings : public UObject +{ + GENERATED_BODY() + +public: + UOculusXRMR_Settings(const FObjectInitializer& ObjectInitializer); + + /** Specify the distance to the camera which divide the background and foreground in MxR casting. + * Set it to CR_TrackingReference to use the distance to the Tracking Reference, which works better + * in the stationary experience. Set it to CR_Head would use the distance to the HMD, which works better + * in the room scale experience. + */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite) + EOculusXRMR_ClippingReference ClippingReference; + + /** The casting viewports would use the same resolution of the camera which used in the calibration process. */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite) + bool bUseTrackedCameraResolution; + + /** When bUseTrackedCameraResolution is false, the width of each casting viewport */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite) + int WidthPerView; + + /** When bUseTrackedCameraResolution is false, the height of each casting viewport */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite) + int HeightPerView; + + /** When CompositionMethod is External Composition, the latency of the casting output which could be adjusted to + * match the camera latency in the external composition application */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0.0", UIMax = "0.1")) + float CastingLatency; + + /** When CompositionMethod is External Composition, the color of the backdrop in the foreground view */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite) + FColor BackdropColor; + + /** When CompositionMethod is Direct Composition, you could adjust this latency to delay the virtual + * hand movement by a small amount of time to match the camera latency */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0.0", UIMax = "0.5")) + float HandPoseStateLatency; + + /** [Green-screen removal] Chroma Key Color. Apply when CompositionMethod is DirectComposition */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite) + FColor ChromaKeyColor; + + /** [Green-screen removal] Chroma Key Similarity. Apply when CompositionMethod is DirectComposition */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0.0", UIMax = "1.0")) + float ChromaKeySimilarity; + + /** [Green-screen removal] Chroma Key Smooth Range. Apply when CompositionMethod is DirectComposition */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0.0", UIMax = "0.2")) + float ChromaKeySmoothRange; + + /** [Green-screen removal] Chroma Key Spill Range. Apply when CompositionMethod is DirectComposition */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite, meta = (UIMin = "0.0", UIMax = "0.2")) + float ChromaKeySpillRange; + + /** Set the amount of post process effects in the MR view for external composition */ + UPROPERTY(Category = MetaXR, EditAnywhere, BlueprintReadWrite) + EOculusXRMR_PostProcessEffects ExternalCompositionPostProcessEffects; + + + /** ExternalComposition: The casting window includes the background and foreground view + * DirectComposition: The game scene would be composited with the camera frame directly + */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + EOculusXRMR_CompositionMethod GetCompositionMethod() { return CompositionMethod; } + + /** ExternalComposition: The casting window includes the background and foreground view + * DirectComposition: The game scene would be composited with the camera frame directly + */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + void SetCompositionMethod(EOculusXRMR_CompositionMethod val); + + /** When CompositionMethod is DirectComposition, the physical camera device which provide the frame */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + EOculusXRMR_CameraDeviceEnum GetCapturingCamera() { return CapturingCamera; } + + /** When CompositionMethod is DirectComposition, the physical camera device which provide the frame */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + void SetCapturingCamera(EOculusXRMR_CameraDeviceEnum val); + + /** Is MRC on and off */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + bool GetIsCasting() { return bIsCasting; } + + /** Turns MRC on and off */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + void SetIsCasting(bool val); + + /** Bind the casting camera to the calibrated external camera. + * (Requires a calibrated external camera) + */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + void BindToTrackedCameraIndexIfAvailable(int InTrackedCameraIndex); + + UFUNCTION(BlueprintCallable, Category = MetaXR) + int GetBindToTrackedCameraIndex() { return BindToTrackedCameraIndex; } + + /** Load settings from the config file */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + void LoadFromIni(); + + /** Save settings to the config file */ + UFUNCTION(BlueprintCallable, Category = MetaXR) + void SaveToIni() const; + +private: + /** Turns MRC on and off (does not get saved to or loaded from ini) */ + UPROPERTY() + bool bIsCasting; + + /** ExternalComposition: The casting window includes the background and foreground view + * DirectComposition: The game scene would be composited with the camera frame directly + */ + UPROPERTY() + EOculusXRMR_CompositionMethod CompositionMethod; + + /** When CompositionMethod is DirectComposition, the physical camera device which provide the frame */ + UPROPERTY() + EOculusXRMR_CameraDeviceEnum CapturingCamera; + + /** Tracked camera that we want to bind the in-game MR camera to*/ + int BindToTrackedCameraIndex; + + DECLARE_DELEGATE_TwoParams(OnCompositionMethodChangeDelegate, EOculusXRMR_CompositionMethod, EOculusXRMR_CompositionMethod); + DECLARE_DELEGATE_TwoParams(OnCapturingCameraChangeDelegate, EOculusXRMR_CameraDeviceEnum, EOculusXRMR_CameraDeviceEnum); + DECLARE_DELEGATE_TwoParams(OnBooleanSettingChangeDelegate, bool, bool); + DECLARE_DELEGATE_TwoParams(OnIntegerSettingChangeDelegate, int, int); + + OnIntegerSettingChangeDelegate TrackedCameraIndexChangeDelegate; + OnCompositionMethodChangeDelegate CompositionMethodChangeDelegate; + OnCapturingCameraChangeDelegate CapturingCameraChangeDelegate; + OnBooleanSettingChangeDelegate IsCastingChangeDelegate; + + // Give the OculusXRMR module access to the delegates so that + friend class FOculusXRMRModule; +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRMovement/OculusXRMovement.Build.cs b/Plugins/OculusXR/Source/OculusXRMovement/OculusXRMovement.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..e9be6a4e21df46f402b88a751a2ab4e4f13da3dd --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/OculusXRMovement.Build.cs @@ -0,0 +1,29 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class OculusXRMovement : ModuleRules + { + public OculusXRMovement(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "ApplicationCore", + "Engine", + "InputCore", + "HeadMountedDisplay", + "OVRPluginXR", + "OculusXRHMD", + }); + + PrivateIncludePaths.AddRange( + new string[] { + // Relative to Engine\Plugins\Runtime\OculusXR\OculusXRVR\Source + "OculusXRHMD/Private", + }); + } + } +} diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRBodyTrackingComponent.cpp b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRBodyTrackingComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..efb90f12c82a9b91a86f3f174bd854985eebb97c --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRBodyTrackingComponent.cpp @@ -0,0 +1,244 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#include "OculusXRBodyTrackingComponent.h" + +#include "Engine/SkeletalMesh.h" +#include "DrawDebugHelpers.h" +#include "OculusXRHMD.h" +#include "OculusXRPluginWrapper.h" +#include "OculusXRMovementFunctionLibrary.h" +#include "OculusXRMovementLog.h" + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) +static TAutoConsoleVariable<int32> CVarOVRBodyDebugDraw( + TEXT("ovr.BodyDebugDraw"), + 0, + TEXT("Enables or disables debug drawing for body tracking.\n") + TEXT("<=0: disabled (no drawing)\n") + TEXT(" 1: enabled (debug drawing)\n")); +#endif + +int UOculusXRBodyTrackingComponent::TrackingInstanceCount = 0; + +UOculusXRBodyTrackingComponent::UOculusXRBodyTrackingComponent() + : BodyTrackingMode(EOculusXRBodyTrackingMode::PositionAndRotation) + , ConfidenceThreshold(0.f) + , WorldToMeters(100.f) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + + // Setup defaults + BoneNames.Add(EOculusXRBoneID::BodyRoot, "Root"); + BoneNames.Add(EOculusXRBoneID::BodyHips, "Hips"); + BoneNames.Add(EOculusXRBoneID::BodySpineLower, "SpineLower"); + BoneNames.Add(EOculusXRBoneID::BodySpineMiddle, "SpineMiddle"); + BoneNames.Add(EOculusXRBoneID::BodySpineUpper, "SpineUpper"); + BoneNames.Add(EOculusXRBoneID::BodyChest, "Chest"); + BoneNames.Add(EOculusXRBoneID::BodyNeck, "Neck"); + BoneNames.Add(EOculusXRBoneID::BodyHead, "Head"); + BoneNames.Add(EOculusXRBoneID::BodyLeftShoulder, "LeftShoulder"); + BoneNames.Add(EOculusXRBoneID::BodyLeftScapula, "LeftScapula"); + BoneNames.Add(EOculusXRBoneID::BodyLeftArmUpper, "LeftArmUpper"); + BoneNames.Add(EOculusXRBoneID::BodyLeftArmLower, "LeftArmLower"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandWristTwist, "LeftHandWristTwist"); + BoneNames.Add(EOculusXRBoneID::BodyRightShoulder, "RightShoulder"); + BoneNames.Add(EOculusXRBoneID::BodyRightScapula, "RightScapula"); + BoneNames.Add(EOculusXRBoneID::BodyRightArmUpper, "RightArmUpper"); + BoneNames.Add(EOculusXRBoneID::BodyRightArmLower, "RightArmLower"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandWristTwist, "RightHandWristTwist"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandPalm, "LeftHandPalm"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandWrist, "LeftHandWrist"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandThumbMetacarpal, "LeftHandThumbMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandThumbProximal, "LeftHandThumbProximal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandThumbDistal, "LeftHandThumbDistal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandThumbTip, "LeftHandThumbTip"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandIndexMetacarpal, "LeftHandIndexMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandIndexProximal, "LeftHandIndexProximal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandIndexIntermediate, "LeftHandIndexIntermediate"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandIndexDistal, "LeftHandIndexDistal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandIndexTip, "LeftHandIndexTip"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandMiddleMetacarpal, "LeftHandMiddleMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandMiddleProximal, "LeftHandMiddleProximal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandMiddleIntermediate, "LeftHandMiddleIntermediate"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandMiddleDistal, "LeftHandMiddleDistal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandMiddleTip, "LeftHandMiddleTip"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandRingMetacarpal, "LeftHandRingMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandRingProximal, "LeftHandRingProximal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandRingIntermediate, "LeftHandRingIntermediate"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandRingDistal, "LeftHandRingDistal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandRingTip, "LeftHandRingTip"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandLittleMetacarpal, "LeftHandLittleMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandLittleProximal, "LeftHandLittleProximal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandLittleIntermediate, "LeftHandLittleIntermediate"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandLittleDistal, "LeftHandLittleDistal"); + BoneNames.Add(EOculusXRBoneID::BodyLeftHandLittleTip, "LeftHandLittleTip"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandPalm, "RightHandPalm"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandWrist, "RightHandWrist"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandThumbMetacarpal, "RightHandThumbMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandThumbProximal, "RightHandThumbProximal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandThumbDistal, "RightHandThumbDistal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandThumbTip, "RightHandThumbTip"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandIndexMetacarpal, "RightHandIndexMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandIndexProximal, "RightHandIndexProximal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandIndexIntermediate, "RightHandIndexIntermediate"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandIndexDistal, "RightHandIndexDistal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandIndexTip, "RightHandIndexTip"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandMiddleMetacarpal, "RightHandMiddleMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandMiddleProximal, "RightHandMiddleProximal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandMiddleIntermediate, "RightHandMiddleIntermediate"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandMiddleDistal, "RightHandMiddleDistal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandMiddleTip, "RightHandMiddleTip"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandRingMetacarpal, "RightHandRingMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandRingProximal, "RightHandRingProximal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandRingIntermediate, "RightHandRingIntermediate"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandRingDistal, "RightHandRingDistal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandRingTip, "RightHandRingTip"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandLittleMetacarpal, "RightHandLittleMetacarpal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandLittleProximal, "RightHandLittleProximal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandLittleIntermediate, "RightHandLittleIntermediate"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandLittleDistal, "RightHandLittleDistal"); + BoneNames.Add(EOculusXRBoneID::BodyRightHandLittleTip, "RightHandLittleTip"); +} + +void UOculusXRBodyTrackingComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (!UOculusXRMovementFunctionLibrary::IsBodyTrackingSupported()) + { + // Early exit if body tracking isn't supported + UE_LOG(LogOculusXRMovement, Warning, TEXT("Body tracking is not supported. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + + if (!OculusXRHMD::GetUnitScaleFactorFromSettings(GetWorld(), WorldToMeters)) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot get world settings. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + } + + if (!InitializeBodyBones()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to initialize body data. (%s: %s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + + if (!UOculusXRMovementFunctionLibrary::StartBodyTracking()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to start body tracking. (%s: %s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + ++TrackingInstanceCount; +} + +void UOculusXRBodyTrackingComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (IsComponentTickEnabled()) + { + if (--TrackingInstanceCount == 0) + { + if (!UOculusXRMovementFunctionLibrary::StopBodyTracking()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to stop body tracking. (%s: %s)"), *GetOwner()->GetName(), *GetName()); + } + } + } + + Super::EndPlay(EndPlayReason); +} + +void UOculusXRBodyTrackingComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if(UOculusXRMovementFunctionLibrary::TryGetBodyState(BodyState, WorldToMeters)) + { + if (BodyState.IsActive && BodyState.Confidence > ConfidenceThreshold) + { + for (int i = 0; i < BodyState.Joints.Num(); ++i) + { + const FOculusXRBodyJoint& Joint = BodyState.Joints[i]; + const FVector& Position = Joint.Position; + const FRotator& Orientation = Joint.Orientation; + +#if !(UE_BUILD_SHIPPING || UE_BUILD_TEST) + if (CVarOVRBodyDebugDraw.GetValueOnGameThread() > 0) + { + const FTransform& ParentTransform = GetOwner()->GetActorTransform(); + + FVector DebugPosition = ParentTransform.TransformPosition(Position); + FRotator DebugOrientation = ParentTransform.TransformRotation(Orientation.Quaternion()).Rotator(); + + DrawDebugLine(GetWorld(), DebugPosition, DebugPosition + DebugOrientation.Quaternion().GetUpVector(), FColor::Blue); + DrawDebugLine(GetWorld(), DebugPosition, DebugPosition + DebugOrientation.Quaternion().GetForwardVector(), FColor::Red); + DrawDebugLine(GetWorld(), DebugPosition, DebugPosition + DebugOrientation.Quaternion().GetRightVector(), FColor::Green); + } +#endif + + int32* BoneIndex = MappedBoneIndices.Find(static_cast<EOculusXRBoneID>(i)); + if (BoneIndex != nullptr) + { + switch (BodyTrackingMode) + { + case EOculusXRBodyTrackingMode::PositionAndRotation: + SetBoneTransformByName(BoneNames[static_cast<EOculusXRBoneID>(i)], FTransform(Orientation, Position), EBoneSpaces::ComponentSpace); + break; + case EOculusXRBodyTrackingMode::RotationOnly: + SetBoneRotationByName(BoneNames[static_cast<EOculusXRBoneID>(i)], Orientation, EBoneSpaces::ComponentSpace); + break; + case EOculusXRBodyTrackingMode::NoTracking: + break; + } + } + } + } + } + else + { + UE_LOG(LogOculusXRMovement, Verbose, TEXT("Failed to get body state (%s:%s)."), *GetOwner()->GetName(), *GetName()); + } +} + +void UOculusXRBodyTrackingComponent::ResetAllBoneTransforms() +{ + for (int i = 0; i < BodyState.Joints.Num(); ++i) + { + int32* BoneIndex = MappedBoneIndices.Find(static_cast<EOculusXRBoneID>(i)); + if (BoneIndex != nullptr) + { + ResetBoneTransformByName(BoneNames[static_cast<EOculusXRBoneID>(i)]); + } + } +} + +bool UOculusXRBodyTrackingComponent::InitializeBodyBones() +{ + if (SkeletalMesh == nullptr) + { + UE_LOG(LogOculusXRMovement, Display, TEXT("No SkeletalMesh in this component.")); + return false; + } + + for (const auto& it : BoneNames) + { + int32 BoneIndex = GetBoneIndex(it.Value); + + if (BoneIndex == INDEX_NONE) + { + UE_LOG(LogOculusXRMovement, Display, TEXT("Could not find bone %s in skeletal mesh %s"), *StaticEnum<EOculusXRBoneID>()->GetValueAsString(it.Key), *SkeletalMesh->GetName()); + } + else + { + MappedBoneIndices.Add(it.Key, BoneIndex); + } + } + + return true; +} diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXREyeTrackingComponent.cpp b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXREyeTrackingComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ead645cd8a1659acf3a6749bb92f58aa6419a402 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXREyeTrackingComponent.cpp @@ -0,0 +1,200 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#include "OculusXREyeTrackingComponent.h" + +#include "GameFramework/WorldSettings.h" +#include "GameFramework/PlayerController.h" +#include "OculusXRHMDPrivate.h" +#include "OculusXRPluginWrapper.h" +#include "OculusXRMovementFunctionLibrary.h" +#include "OculusXRMovementHelpers.h" +#include "OculusXRMovementLog.h" + +int UOculusXREyeTrackingComponent::TrackingInstanceCount = 0; + +UOculusXREyeTrackingComponent::UOculusXREyeTrackingComponent() + : TargetMeshComponentName(NAME_None) + , bUpdatePosition(true) + , bUpdateRotation(true) + , ConfidenceThreshold(0.f) + , bAcceptInvalid(false) + , WorldToMeters(100.f) + , TargetPoseableMeshComponent(nullptr) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + + EyeToBone.Add(EOculusXREye::Left, "LeftEye"); + EyeToBone.Add(EOculusXREye::Right, "RightEye"); +} + +void UOculusXREyeTrackingComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (!UOculusXRMovementFunctionLibrary::IsEyeTrackingSupported()) + { + // Early exit if eye tracking isn't supported + UE_LOG(LogOculusXRMovement, Warning, TEXT("Eye tracking is not supported. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + + // Try & check initializing the eye data + if (!InitializeEyes()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to initialize eye tracking data. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + } + + if (!UOculusXRMovementFunctionLibrary::StartEyeTracking()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to start eye tracking. (%s: %s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + ++TrackingInstanceCount; +} + +void UOculusXREyeTrackingComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (IsComponentTickEnabled()) + { + if (--TrackingInstanceCount == 0) + { + if (!UOculusXRMovementFunctionLibrary::StopEyeTracking()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to stop eye tracking. (%s: %s)"), *GetOwner()->GetName(), *GetName()); + } + } + } + + Super::EndPlay(EndPlayReason); +} + +void UOculusXREyeTrackingComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (!IsValid(TargetPoseableMeshComponent)) + { + UE_LOG(LogOculusXRMovement, VeryVerbose, TEXT("No target mesh specified. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + + FOculusXREyeGazesState EyeGazesState; + + if (UOculusXRMovementFunctionLibrary::TryGetEyeGazesState(EyeGazesState, WorldToMeters)) + { + for (uint8 i = 0u; i < static_cast<uint8>(EOculusXREye::COUNT); ++i) + { + if (PerEyeData[i].EyeIsMapped) + { + const auto& Bone = PerEyeData[i].MappedBoneName; + const auto& EyeGaze = EyeGazesState.EyeGazes[i]; + if ((bAcceptInvalid || EyeGaze.bIsValid) && (EyeGaze.Confidence >= ConfidenceThreshold)) + { + int32 BoneIndex = TargetPoseableMeshComponent->GetBoneIndex(Bone); + FTransform CurrentTransform = TargetPoseableMeshComponent->GetBoneTransformByName(Bone, EBoneSpaces::ComponentSpace); + + if (bUpdatePosition) + { + CurrentTransform.SetLocation(EyeGaze.Position); + } + + if (bUpdateRotation) + { + CurrentTransform.SetRotation(EyeGaze.Orientation.Quaternion() * PerEyeData[i].InitialRotation); + } + + TargetPoseableMeshComponent->SetBoneTransformByName(Bone, CurrentTransform, EBoneSpaces::ComponentSpace); + } + } + } + } + else + { + UE_LOG(LogOculusXRMovement, VeryVerbose, TEXT("Failed to get Eye state from EyeTrackingComponent. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + } +} + +void UOculusXREyeTrackingComponent::ClearRotationValues() +{ + if (!IsValid(TargetPoseableMeshComponent)) + { + UE_LOG(LogOculusXRMovement, VeryVerbose, TEXT("No target mesh specified. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + return; + } + + for (uint8 i = 0u; i < static_cast<uint8>(EOculusXREye::COUNT); ++i) + { + if (PerEyeData[i].EyeIsMapped) + { + const auto& Bone = PerEyeData[i].MappedBoneName; + + int32 BoneIndex = TargetPoseableMeshComponent->GetBoneIndex(Bone); + FTransform CurrentTransform = TargetPoseableMeshComponent->GetBoneTransformByName(Bone, EBoneSpaces::ComponentSpace); + + CurrentTransform.SetRotation(PerEyeData[i].InitialRotation); + + TargetPoseableMeshComponent->SetBoneTransformByName(Bone, CurrentTransform, EBoneSpaces::ComponentSpace); + } + } +} + +bool UOculusXREyeTrackingComponent::InitializeEyes() +{ + bool bIsAnythingMapped = false; + + TargetPoseableMeshComponent = OculusXRUtility::FindComponentByName<UPoseableMeshComponent>(GetOwner(), TargetMeshComponentName); + + if (!IsValid(TargetPoseableMeshComponent)) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Could not find mesh with name (%s) for component. (%s:%s)"), *TargetMeshComponentName.ToString(), *GetOwner()->GetName(), *GetName()); + return false; + } + + for (uint8 i = 0u; i < static_cast<uint8>(EOculusXREye::COUNT); ++i) + { + const EOculusXREye Eye = static_cast<EOculusXREye>(i); + const FName* BoneNameForThisEye = EyeToBone.Find(Eye); + PerEyeData[i].EyeIsMapped = (nullptr != BoneNameForThisEye); + + if (PerEyeData[i].EyeIsMapped) + { + int32 BoneIndex = TargetPoseableMeshComponent->GetBoneIndex(*BoneNameForThisEye); + if (BoneIndex == INDEX_NONE) + { + PerEyeData[i].EyeIsMapped = false; // Eye is explicitly mapped to a bone. But the bone name doesn't exist. + UE_LOG(LogOculusXRMovement, Warning, TEXT("Could not find bone by name (%s) in mesh %s. (%s:%s)"), *BoneNameForThisEye->ToString(), *TargetPoseableMeshComponent->GetName(), *GetOwner()->GetName(), *GetName()); + } + else + { + PerEyeData[i].MappedBoneName = *BoneNameForThisEye; + PerEyeData[i].InitialRotation = TargetPoseableMeshComponent->GetBoneTransformByName(*BoneNameForThisEye, EBoneSpaces::ComponentSpace).GetRotation(); + bIsAnythingMapped = true; + } + } + else + { + UE_LOG(LogOculusXRMovement, Display, TEXT("Eye (%s) is not mapped to any bone on mesh (%s)"), *StaticEnum<EOculusXREye>()->GetValueAsString(Eye), *TargetPoseableMeshComponent->GetName()); + } + } + + if (!bIsAnythingMapped) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Component name -- %s:%s, doesn't have a valid configuration."), *GetOwner()->GetName(), *GetName()); + } + + if (!OculusXRHMD::GetUnitScaleFactorFromSettings(GetWorld(), WorldToMeters)) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot get world settings. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + } + + return bIsAnythingMapped; +} diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRFaceTrackingComponent.cpp b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRFaceTrackingComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8c87a1c0a0099a0863c5a50a3f344c415994d52f --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRFaceTrackingComponent.cpp @@ -0,0 +1,243 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ +#include "OculusXRFaceTrackingComponent.h" +#include "OculusXRHMD.h" +#include "OculusXRPluginWrapper.h" +#include "OculusXRMovementFunctionLibrary.h" +#include "OculusXRMovementHelpers.h" +#include "OculusXRMovementLog.h" + +int UOculusXRFaceTrackingComponent::TrackingInstanceCount = 0; + +UOculusXRFaceTrackingComponent::UOculusXRFaceTrackingComponent() + : TargetMeshComponentName(NAME_None) + , InvalidFaceDataResetTime(2.0f) + , bUpdateFace(true) + , TargetMeshComponent(nullptr) +{ + PrimaryComponentTick.bCanEverTick = true; + PrimaryComponentTick.bStartWithTickEnabled = true; + + // Some defaults + ExpressionNames.Add(EOculusXRFaceExpression::BrowLowererL, "browLowerer_L"); + ExpressionNames.Add(EOculusXRFaceExpression::BrowLowererR, "browLowerer_R"); + ExpressionNames.Add(EOculusXRFaceExpression::CheekPuffL, "cheekPuff_L"); + ExpressionNames.Add(EOculusXRFaceExpression::CheekPuffR, "cheekPuff_R"); + ExpressionNames.Add(EOculusXRFaceExpression::CheekRaiserL, "cheekRaiser_L"); + ExpressionNames.Add(EOculusXRFaceExpression::CheekRaiserR, "cheekRaiser_R"); + ExpressionNames.Add(EOculusXRFaceExpression::CheekSuckL, "cheekSuck_L"); + ExpressionNames.Add(EOculusXRFaceExpression::CheekSuckR, "cheekSuck_R"); + ExpressionNames.Add(EOculusXRFaceExpression::ChinRaiserB, "chinRaiserB"); + ExpressionNames.Add(EOculusXRFaceExpression::ChinRaiserT, "chinRaiserT"); + ExpressionNames.Add(EOculusXRFaceExpression::DimplerL, "dimpler_L"); + ExpressionNames.Add(EOculusXRFaceExpression::DimplerR, "dimpler_R"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesClosedL, "eyesClosed_L"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesClosedR, "eyesClosed_R"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesLookDownL, "eyesLookDown_L"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesLookDownR, "eyesLookDown_R"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesLookLeftL, "eyesLookLeft_L"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesLookLeftR, "eyesLookLeft_R"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesLookRightL, "eyesLookRight_L"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesLookRightR, "eyesLookRight_R"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesLookUpL, "eyesLookUp_L"); + ExpressionNames.Add(EOculusXRFaceExpression::EyesLookUpR, "eyesLookUp_R"); + ExpressionNames.Add(EOculusXRFaceExpression::InnerBrowRaiserL, "innerBrowRaiser_L"); + ExpressionNames.Add(EOculusXRFaceExpression::InnerBrowRaiserR, "innerBrowRaiser_R"); + ExpressionNames.Add(EOculusXRFaceExpression::JawDrop, "jawDrop"); + ExpressionNames.Add(EOculusXRFaceExpression::JawSidewaysLeft, "jawSidewaysLeft"); + ExpressionNames.Add(EOculusXRFaceExpression::JawSidewaysRight, "jawSidewaysRight"); + ExpressionNames.Add(EOculusXRFaceExpression::JawThrust, "jawThrust"); + ExpressionNames.Add(EOculusXRFaceExpression::LidTightenerL, "lidTightener_L"); + ExpressionNames.Add(EOculusXRFaceExpression::LidTightenerR, "lidTightener_R"); + ExpressionNames.Add(EOculusXRFaceExpression::LipCornerDepressorL, "lipCornerDepressor_L"); + ExpressionNames.Add(EOculusXRFaceExpression::LipCornerDepressorR, "lipCornerDepressor_R"); + ExpressionNames.Add(EOculusXRFaceExpression::LipCornerPullerL, "lipCornerPuller_L"); + ExpressionNames.Add(EOculusXRFaceExpression::LipCornerPullerR, "lipCornerPuller_R"); + ExpressionNames.Add(EOculusXRFaceExpression::LipFunnelerLB, "lipFunnelerLB"); + ExpressionNames.Add(EOculusXRFaceExpression::LipFunnelerLT, "lipFunnelerLT"); + ExpressionNames.Add(EOculusXRFaceExpression::LipFunnelerRB, "lipFunnelerRB"); + ExpressionNames.Add(EOculusXRFaceExpression::LipFunnelerRT, "lipFunnelerRT"); + ExpressionNames.Add(EOculusXRFaceExpression::LipPressorL, "lipPressor_L"); + ExpressionNames.Add(EOculusXRFaceExpression::LipPressorR, "lipPressor_R"); + ExpressionNames.Add(EOculusXRFaceExpression::LipPuckerL, "lipPucker_L"); + ExpressionNames.Add(EOculusXRFaceExpression::LipPuckerR, "lipPucker_R"); + ExpressionNames.Add(EOculusXRFaceExpression::LipStretcherL, "lipStretcher_L"); + ExpressionNames.Add(EOculusXRFaceExpression::LipStretcherR, "lipStretcher_R"); + ExpressionNames.Add(EOculusXRFaceExpression::LipSuckLB, "lipSuckLB"); + ExpressionNames.Add(EOculusXRFaceExpression::LipSuckLT, "lipSuckLT"); + ExpressionNames.Add(EOculusXRFaceExpression::LipSuckRB, "lipSuckRB"); + ExpressionNames.Add(EOculusXRFaceExpression::LipSuckRT, "lipSuckRT"); + ExpressionNames.Add(EOculusXRFaceExpression::LipTightenerL, "lipTightener_L"); + ExpressionNames.Add(EOculusXRFaceExpression::LipTightenerR, "lipTightener_R"); + ExpressionNames.Add(EOculusXRFaceExpression::LipsToward, "lipsToward"); + ExpressionNames.Add(EOculusXRFaceExpression::LowerLipDepressorL, "lowerLipDepressor_L"); + ExpressionNames.Add(EOculusXRFaceExpression::LowerLipDepressorR, "lowerLipDepressor_R"); + ExpressionNames.Add(EOculusXRFaceExpression::MouthLeft, "mouthLeft"); + ExpressionNames.Add(EOculusXRFaceExpression::MouthRight, "mouthRight"); + ExpressionNames.Add(EOculusXRFaceExpression::NoseWrinklerL, "noseWrinkler_L"); + ExpressionNames.Add(EOculusXRFaceExpression::NoseWrinklerR, "noseWrinkler_R"); + ExpressionNames.Add(EOculusXRFaceExpression::OuterBrowRaiserL, "outerBrowRaiser_L"); + ExpressionNames.Add(EOculusXRFaceExpression::OuterBrowRaiserR, "outerBrowRaiser_R"); + ExpressionNames.Add(EOculusXRFaceExpression::UpperLidRaiserL, "upperLidRaiser_L"); + ExpressionNames.Add(EOculusXRFaceExpression::UpperLidRaiserR, "upperLidRaiser_R"); + ExpressionNames.Add(EOculusXRFaceExpression::UpperLipRaiserL, "upperLipRaiser_L"); + ExpressionNames.Add(EOculusXRFaceExpression::UpperLipRaiserR, "upperLipRaiser_R"); +} + +void UOculusXRFaceTrackingComponent::BeginPlay() +{ + Super::BeginPlay(); + + if (!UOculusXRMovementFunctionLibrary::IsFaceTrackingSupported()) + { + // Early exit if face tracking isn't supported + UE_LOG(LogOculusXRMovement, Warning, TEXT("Face tracking is not supported. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + + if (TargetMeshComponentName == NAME_None) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Invalid mesh component name. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + + if (!InitializeFaceTracking()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to initialize face tracking. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + + if (!UOculusXRMovementFunctionLibrary::StartFaceTracking()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to start face tracking. (%s: %s)"), *GetOwner()->GetName(), *GetName()); + SetComponentTickEnabled(false); + return; + } + ++TrackingInstanceCount; +} + +void UOculusXRFaceTrackingComponent::EndPlay(const EEndPlayReason::Type EndPlayReason) +{ + if (IsComponentTickEnabled()) + { + if (--TrackingInstanceCount == 0) + { + if (!UOculusXRMovementFunctionLibrary::StopFaceTracking()) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Failed to stop face tracking. (%s: %s)"), *GetOwner()->GetName(), *GetName()); + } + } + } + + Super::EndPlay(EndPlayReason); +} + +void UOculusXRFaceTrackingComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + if (!IsValid(TargetMeshComponent)) + { + UE_LOG(LogOculusXRMovement, VeryVerbose, TEXT("No target mesh specified. (%s:%s)"), *GetOwner()->GetName(), *GetName()); + return; + } + + if (UOculusXRMovementFunctionLibrary::TryGetFaceState(FaceState) && bUpdateFace) + { + InvalidFaceStateTimer = 0.0f; + + MorphTargets.ResetMorphTargetCurves(TargetMeshComponent); + + for (int32 FaceExpressionIndex = 0; FaceExpressionIndex < static_cast<int32>(EOculusXRFaceExpression::COUNT); ++FaceExpressionIndex) + { + if (ExpressionValid[FaceExpressionIndex]) + { + FName ExpressionName = ExpressionNames[static_cast<EOculusXRFaceExpression>(FaceExpressionIndex)]; + MorphTargets.SetMorphTarget(ExpressionName, FaceState.ExpressionWeights[FaceExpressionIndex]); + } + } + } + else + { + InvalidFaceStateTimer += DeltaTime; + if (InvalidFaceStateTimer >= InvalidFaceDataResetTime) + { + MorphTargets.ResetMorphTargetCurves(TargetMeshComponent); + } + } + + MorphTargets.ApplyMorphTargets(TargetMeshComponent); +} + +void UOculusXRFaceTrackingComponent::SetExpressionValue(EOculusXRFaceExpression Expression, float Value) +{ + if (Expression >= EOculusXRFaceExpression::COUNT) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot set expression value with invalid expression index.")); + return; + } + + if (!ExpressionValid[static_cast<int32>(Expression)]) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot set expression value for an expression with an invalid associated morph target name. Expression name: %s"), *StaticEnum<EOculusXRFaceExpression>()->GetValueAsString(Expression)); + return; + } + + FName ExpressionName = ExpressionNames[Expression]; + MorphTargets.SetMorphTarget(ExpressionName, Value); +} + +float UOculusXRFaceTrackingComponent::GetExpressionValue(EOculusXRFaceExpression Expression) const +{ + if (Expression >= EOculusXRFaceExpression::COUNT) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot request expression value using an invalid expression index.")); + return 0.0f; + } + + FName ExpressionName = ExpressionNames[Expression]; + if (ExpressionName == NAME_None) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Cannot request expression value for an expression with an invalid associated morph target name. Expression name: %s"), *StaticEnum<EOculusXRFaceExpression>()->GetValueAsString(Expression)); + return 0.0f; + } + + return MorphTargets.GetMorphTarget(ExpressionName); +} + +void UOculusXRFaceTrackingComponent::ClearExpressionValues() +{ + MorphTargets.ClearMorphTargets(); +} + +bool UOculusXRFaceTrackingComponent::InitializeFaceTracking() +{ + TargetMeshComponent = OculusXRUtility::FindComponentByName<USkinnedMeshComponent>(GetOwner(), TargetMeshComponentName); + + if (!IsValid(TargetMeshComponent)) + { + UE_LOG(LogOculusXRMovement, Warning, TEXT("Could not find skeletal mesh component with name: (%s). (%s:%s)"), *TargetMeshComponentName.ToString(), *GetOwner()->GetName(), *GetName()); + return false; + } + + if (TargetMeshComponent && TargetMeshComponent->SkeletalMesh) + { + const TMap<FName, int32>& MorphTargetIndexMap = TargetMeshComponent->SkeletalMesh->GetMorphTargetIndexMap(); + + for (const auto& it : ExpressionNames) + { + ExpressionValid[static_cast<int32>(it.Key)] = MorphTargetIndexMap.Contains(it.Value); + } + + return true; + } + + return false; +} diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMorphTargetsController.cpp b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMorphTargetsController.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9b6055da56c4cf72114da18ebff4d9a55e8d53ed --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMorphTargetsController.cpp @@ -0,0 +1,91 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRMorphTargetsController.h" + +#include "AnimationRuntime.h" + +void FOculusXRMorphTargetsController::ResetMorphTargetCurves(USkinnedMeshComponent* TargetMeshComponent) +{ + if (TargetMeshComponent) + { + TargetMeshComponent->ActiveMorphTargets.Reset(); + + if (TargetMeshComponent->SkeletalMesh) + { + TargetMeshComponent->MorphTargetWeights.SetNum(TargetMeshComponent->SkeletalMesh->GetMorphTargets().Num()); + + // we need this code to ensure the buffer gets cleared whether or not you have morphtarget curve set + // the case, where you had morphtargets weight on, and when you clear the weight, you want to make sure + // the buffer gets cleared and resized + if (TargetMeshComponent->MorphTargetWeights.Num() > 0) + { + FMemory::Memzero(TargetMeshComponent->MorphTargetWeights.GetData(), TargetMeshComponent->MorphTargetWeights.GetAllocatedSize()); + } + } + else + { + TargetMeshComponent->MorphTargetWeights.Reset(); + } + } +} + +void FOculusXRMorphTargetsController::ApplyMorphTargets(USkinnedMeshComponent* TargetMeshComponent) +{ + if (TargetMeshComponent && TargetMeshComponent->SkeletalMesh) + { + if (MorphTargetCurves.Num() > 0) + { + FAnimationRuntime::AppendActiveMorphTargets(TargetMeshComponent->SkeletalMesh, MorphTargetCurves, TargetMeshComponent->ActiveMorphTargets, TargetMeshComponent->MorphTargetWeights); + } + } +} + +void FOculusXRMorphTargetsController::SetMorphTarget(FName MorphTargetName, float Value) +{ + float* CurveValPtr = MorphTargetCurves.Find(MorphTargetName); + bool bShouldAddToList = FPlatformMath::Abs(Value) > ZERO_ANIMWEIGHT_THRESH; + if (bShouldAddToList) + { + if (CurveValPtr) + { + // sum up, in the future we might normalize, but for now this just sums up + // this won't work well if all of them have full weight - i.e. additive + *CurveValPtr = Value; + } + else + { + MorphTargetCurves.Add(MorphTargetName, Value); + } + } + // if less than ZERO_ANIMWEIGHT_THRESH + // no reason to keep them on the list + else + { + // remove if found + MorphTargetCurves.Remove(MorphTargetName); + } +} + +float FOculusXRMorphTargetsController::GetMorphTarget(FName MorphTargetName) const +{ + const float* CurveValPtr = MorphTargetCurves.Find(MorphTargetName); + + if (CurveValPtr) + { + return *CurveValPtr; + } + else + { + return 0.0f; + } +} + +void FOculusXRMorphTargetsController::ClearMorphTargets() +{ + MorphTargetCurves.Empty(); +} diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovement.cpp b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovement.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9f36df6536aeaaca3df8fd1359cca887a1ab3078 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovement.cpp @@ -0,0 +1,238 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRMovement.h" +#include "OculusXRMovementModule.h" +#include "OculusXRHMDPrivate.h" +#include "OculusXRHMD.h" +#include "OculusXRPluginWrapper.h" +#include "Logging/MessageLog.h" + +#define LOCTEXT_NAMESPACE "OculusXRMovement" + +namespace XRSpaceFlags +{ + const uint64 XR_SPACE_LOCATION_ORIENTATION_VALID_BIT = 0x00000001; + const uint64 XR_SPACE_LOCATION_POSITION_VALID_BIT = 0x00000002; +} // namespace XRSpaceFlags + +bool OculusXRMovement::GetBodyState(FOculusXRBodyState& outOculusXRBodyState, float WorldToMeters) +{ + static_assert(ovrpBoneId_Body_End == (int)EOculusXRBoneID::COUNT, "The size of the OVRPlugin Bone ID enum should be the same as the EOculusXRBoneID enum."); + + checkf(outOculusXRBodyState.Joints.Num() >= ovrpBoneId_Body_End, TEXT("Not enough joints in FOculusXRBodyState::Joints array. You must have at least %d joints"), ovrpBoneId_Body_End); + + ovrpBodyState OVRBodyState; + ovrpResult OVRBodyStateResult = FOculusXRHMDModule::GetPluginWrapper().GetBodyState(ovrpStep_Render, OVRP_CURRENT_FRAMEINDEX, &OVRBodyState); + ensureMsgf(OVRBodyStateResult != ovrpFailure_NotYetImplemented, TEXT("Body tracking is not implemented on this platform.")); + + if (OVRP_SUCCESS(OVRBodyStateResult)) + { + outOculusXRBodyState.IsActive = (OVRBodyState.IsActive == ovrpBool_True); + outOculusXRBodyState.Confidence = OVRBodyState.Confidence; + outOculusXRBodyState.SkeletonChangedCount = OVRBodyState.SkeletonChangedCount; + outOculusXRBodyState.Time = static_cast<float>(OVRBodyState.Time); + + for (int i = 0; i < ovrpBoneId_Body_End; ++i) + { + ovrpBodyJointLocation OVRJointLocation = OVRBodyState.JointLocations[i]; + ovrpPosef OVRJointPose = OVRJointLocation.Pose; + + FOculusXRBodyJoint& OculusXRBodyJoint = outOculusXRBodyState.Joints[i]; + OculusXRBodyJoint.LocationFlags = OVRJointLocation.LocationFlags; + OculusXRBodyJoint.bIsValid = OVRJointLocation.LocationFlags & (XRSpaceFlags::XR_SPACE_LOCATION_ORIENTATION_VALID_BIT | XRSpaceFlags::XR_SPACE_LOCATION_POSITION_VALID_BIT); + OculusXRBodyJoint.Orientation = FRotator(OculusXRHMD::ToFQuat(OVRJointPose.Orientation)); + OculusXRBodyJoint.Position = OculusXRHMD::ToFVector(OVRJointPose.Position) * WorldToMeters; + } + + return true; + } + + return false; +} + +bool OculusXRMovement::IsBodyTrackingEnabled() +{ + bool bResult = false; + + ovrpBool IsEnabled = ovrpBool_False; + ovrpResult TrackingEnabledResult = FOculusXRHMDModule::GetPluginWrapper().GetBodyTrackingEnabled(&IsEnabled); + + if (OVRP_SUCCESS(TrackingEnabledResult)) + { + bResult = (IsEnabled == ovrpBool_True); + } + + return bResult; +} + +bool OculusXRMovement::IsBodyTrackingSupported() +{ + bool bResult = false; + + ovrpBool IsSupported = ovrpBool_False; + ovrpResult TrackingSupportedResult = FOculusXRHMDModule::GetPluginWrapper().GetBodyTrackingSupported(&IsSupported); + + if (OVRP_SUCCESS(TrackingSupportedResult)) + { + bResult = (IsSupported == ovrpBool_True); + } + + return bResult; +} + +bool OculusXRMovement::StartBodyTracking() +{ + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StartBodyTracking()); +} + +bool OculusXRMovement::StopBodyTracking() +{ + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StopBodyTracking()); +} + +bool OculusXRMovement::GetFaceState(FOculusXRFaceState& outOculusXRFaceState) +{ + static_assert(ovrpFaceExpression_Max == (int)EOculusXRFaceExpression::COUNT, "The size of the OVRPlugin Face Expression enum should be the same as the EOculusXRFaceExpression enum."); + + checkf(outOculusXRFaceState.ExpressionWeightConfidences.Num() >= ovrpFaceConfidence_Max, TEXT("Not enough expression weight confidences in FOculusXRFaceState::ExpressionWeightConfidences. Requires %d available elements in the array."), ovrpFaceConfidence_Max); + checkf(outOculusXRFaceState.ExpressionWeights.Num() >= ovrpFaceExpression_Max, TEXT("Not enough expression weights in FOculusXRFaceState::ExpressionWeights. Requires %d available elements in the array."), ovrpFaceExpression_Max); + + ovrpFaceState OVRFaceState; + ovrpResult OVRFaceStateResult = FOculusXRHMDModule::GetPluginWrapper().GetFaceState(ovrpStep_Render, OVRP_CURRENT_FRAMEINDEX, &OVRFaceState); + ensureMsgf(OVRFaceStateResult != ovrpFailure_NotYetImplemented, TEXT("Face tracking is not implemented on this platform.")); + + if (OVRP_SUCCESS(OVRFaceStateResult)) + { + outOculusXRFaceState.bIsValid = (OVRFaceState.Status.IsValid == ovrpBool_True); + outOculusXRFaceState.bIsEyeFollowingBlendshapesValid = (OVRFaceState.Status.IsEyeFollowingBlendshapesValid == ovrpBool_True); + outOculusXRFaceState.Time = static_cast<float>(OVRFaceState.Time); + + for (int i = 0; i < ovrpFaceExpression_Max; ++i) + { + outOculusXRFaceState.ExpressionWeights[i] = OVRFaceState.ExpressionWeights[i]; + } + + for (int i = 0; i < ovrpFaceConfidence_Max; ++i) + { + outOculusXRFaceState.ExpressionWeightConfidences[i] = OVRFaceState.ExpressionWeightConfidences[i]; + } + + return true; + } + + return false; +} + +bool OculusXRMovement::IsFaceTrackingEnabled() +{ + bool bResult = false; + + ovrpBool IsEnabled = ovrpBool_False; + ovrpResult TrackingEnabledResult = FOculusXRHMDModule::GetPluginWrapper().GetFaceTrackingEnabled(&IsEnabled); + + if (OVRP_SUCCESS(TrackingEnabledResult)) + { + bResult = (IsEnabled == ovrpBool_True); + } + + return bResult; +} + +bool OculusXRMovement::IsFaceTrackingSupported() +{ + bool bResult = false; + + ovrpBool IsSupported = ovrpBool_False; + ovrpResult TrackingSupportedResult = FOculusXRHMDModule::GetPluginWrapper().GetFaceTrackingSupported(&IsSupported); + + if (OVRP_SUCCESS(TrackingSupportedResult)) + { + bResult = (IsSupported == ovrpBool_True); + } + + return bResult; +} + +bool OculusXRMovement::StartFaceTracking() +{ + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StartFaceTracking()); +} + +bool OculusXRMovement::StopFaceTracking() +{ + + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StopFaceTracking()); +} + +bool OculusXRMovement::GetEyeGazesState(FOculusXREyeGazesState& outOculusXREyeGazesState, float WorldToMeters) +{ + static_assert(ovrpEye_Count == (int)EOculusXREye::COUNT, "The size of the OVRPlugin Eye enum should be the same as the EOculusXREye enum."); + + checkf(outOculusXREyeGazesState.EyeGazes.Num() >= ovrpEye_Count, TEXT("Not enough eye gaze states in FOculusXREyeGazesState::EyeGazes. Requires %d available elements in the array."), ovrpEye_Count); + + ovrpEyeGazesState OVREyeGazesState; + ovrpResult OVREyeGazesStateResult = FOculusXRHMDModule::GetPluginWrapper().GetEyeGazesState(ovrpStep_Render, OVRP_CURRENT_FRAMEINDEX, &OVREyeGazesState); + ensureMsgf(OVREyeGazesStateResult != ovrpFailure_NotYetImplemented, TEXT("Eye tracking is not implemented on this platform.")); + + if (OVRP_SUCCESS(OVREyeGazesStateResult)) + { + outOculusXREyeGazesState.Time = static_cast<float>(OVREyeGazesState.Time); + for (int i = 0; i < ovrpEye_Count; ++i) + { + const auto& EyeGazePose = OVREyeGazesState.EyeGazes[i].Pose; + outOculusXREyeGazesState.EyeGazes[i].Orientation = FRotator(OculusXRHMD::ToFQuat(EyeGazePose.Orientation)); + outOculusXREyeGazesState.EyeGazes[i].Position = OculusXRHMD::ToFVector(EyeGazePose.Position) * WorldToMeters; + outOculusXREyeGazesState.EyeGazes[i].bIsValid = (OVREyeGazesState.EyeGazes[i].IsValid == ovrpBool_True); + outOculusXREyeGazesState.EyeGazes[i].Confidence = OVREyeGazesState.EyeGazes[i].Confidence; + } + + return true; + } + + return false; +} + +bool OculusXRMovement::IsEyeTrackingEnabled() +{ + bool bResult = false; + + ovrpBool IsEnabled = ovrpBool_False; + ovrpResult TrackingEnabledResult = FOculusXRHMDModule::GetPluginWrapper().GetEyeTrackingEnabled(&IsEnabled); + + if (OVRP_SUCCESS(TrackingEnabledResult)) + { + bResult = (IsEnabled == ovrpBool_True); + } + + return bResult; +} + +bool OculusXRMovement::IsEyeTrackingSupported() +{ + bool bResult = false; + + ovrpBool IsSupported = ovrpBool_False; + ovrpResult TrackingSupportedResult = FOculusXRHMDModule::GetPluginWrapper().GetEyeTrackingSupported(&IsSupported); + + if (OVRP_SUCCESS(TrackingSupportedResult)) + { + bResult = (IsSupported == ovrpBool_True); + } + + return bResult; +} + +bool OculusXRMovement::StartEyeTracking() +{ + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StartEyeTracking()); +} + +bool OculusXRMovement::StopEyeTracking() +{ + return OVRP_SUCCESS(FOculusXRHMDModule::GetPluginWrapper().StopEyeTracking()); +} diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementFunctionLibrary.cpp b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementFunctionLibrary.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d118e7ce6d08b00b7ff0245ad2136f5f73ccf160 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementFunctionLibrary.cpp @@ -0,0 +1,86 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRMovementFunctionLibrary.h" +#include "OculusXRHMDPrivate.h" +#include "OculusXRMovement.h" +#include "OculusXRHMD.h" + +bool UOculusXRMovementFunctionLibrary::TryGetBodyState(FOculusXRBodyState& outBodyState, float WorldToMeters) +{ + return OculusXRMovement::GetBodyState(outBodyState, WorldToMeters); +} + +bool UOculusXRMovementFunctionLibrary::IsBodyTrackingEnabled() +{ + return OculusXRMovement::IsBodyTrackingEnabled(); +} + +bool UOculusXRMovementFunctionLibrary::IsBodyTrackingSupported() +{ + return OculusXRMovement::IsBodyTrackingSupported(); +} + +bool UOculusXRMovementFunctionLibrary::StartBodyTracking() +{ + return OculusXRMovement::StartBodyTracking(); +} + +bool UOculusXRMovementFunctionLibrary::StopBodyTracking() +{ + return OculusXRMovement::StopBodyTracking(); +} + +bool UOculusXRMovementFunctionLibrary::TryGetFaceState(FOculusXRFaceState& outFaceState) +{ + return OculusXRMovement::GetFaceState(outFaceState); +} + +bool UOculusXRMovementFunctionLibrary::IsFaceTrackingEnabled() +{ + return OculusXRMovement::IsFaceTrackingEnabled(); +} + +bool UOculusXRMovementFunctionLibrary::IsFaceTrackingSupported() +{ + return OculusXRMovement::IsFaceTrackingSupported(); +} + +bool UOculusXRMovementFunctionLibrary::StartFaceTracking() +{ + return OculusXRMovement::StartFaceTracking(); +} + +bool UOculusXRMovementFunctionLibrary::StopFaceTracking() +{ + return OculusXRMovement::StopFaceTracking(); +} + +bool UOculusXRMovementFunctionLibrary::TryGetEyeGazesState(FOculusXREyeGazesState& outEyeGazesState, float WorldToMeters) +{ + return OculusXRMovement::GetEyeGazesState(outEyeGazesState, WorldToMeters); +} + +bool UOculusXRMovementFunctionLibrary::IsEyeTrackingEnabled() +{ + return OculusXRMovement::IsEyeTrackingEnabled(); +} + +bool UOculusXRMovementFunctionLibrary::IsEyeTrackingSupported() +{ + return OculusXRMovement::IsEyeTrackingSupported(); +} + +bool UOculusXRMovementFunctionLibrary::StartEyeTracking() +{ + return OculusXRMovement::StartEyeTracking(); +} + +bool UOculusXRMovementFunctionLibrary::StopEyeTracking() +{ + return OculusXRMovement::StopEyeTracking(); +} diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementLog.h b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementLog.h new file mode 100644 index 0000000000000000000000000000000000000000..3191cfba08e405978bc34e4089a81225e4400aa8 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementLog.h @@ -0,0 +1,12 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRMovement, Log, All); diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementModule.cpp b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2be3fef767cabe7640d9412a8aa0540414dd9f97 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementModule.cpp @@ -0,0 +1,34 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRMovementModule.h" +#include "OculusXRHMDModule.h" +#include "OculusXRMovementLog.h" + +#define LOCTEXT_NAMESPACE "OculusXRMovement" + +DEFINE_LOG_CATEGORY(LogOculusXRMovement); + +//------------------------------------------------------------------------------------------------- +// FOculusXRMovementModule +//------------------------------------------------------------------------------------------------- + +FOculusXRMovementModule::FOculusXRMovementModule() +{ +} + +void FOculusXRMovementModule::StartupModule() +{ +} + +void FOculusXRMovementModule::ShutdownModule() +{ +} + +IMPLEMENT_MODULE(FOculusXRMovementModule, OculusXRMovement) + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementModule.h b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementModule.h new file mode 100644 index 0000000000000000000000000000000000000000..8733d380e560a50c6b03f2237c65e4fc4e6f9b35 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementModule.h @@ -0,0 +1,32 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once +#include "IOculusXRMovementModule.h" +#include "OculusXRMovement.h" + +#define LOCTEXT_NAMESPACE "OculusXRMovement" + +//------------------------------------------------------------------------------------------------- +// FOculusXRMovementModule +//------------------------------------------------------------------------------------------------- + +class FOculusXRMovementModule : public IOculusXRMovementModule +{ +public: + FOculusXRMovementModule(); + + static inline FOculusXRMovementModule& Get() + { + return FModuleManager::LoadModuleChecked<FOculusXRMovementModule>("OculusXRMovement"); + } + + virtual void StartupModule() override; + virtual void ShutdownModule() override; +}; + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementTypes.cpp b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementTypes.cpp new file mode 100644 index 0000000000000000000000000000000000000000..879d43386d0733b56df39321706bbe7b4e916f74 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Private/OculusXRMovementTypes.cpp @@ -0,0 +1,50 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRMovementTypes.h" +#include "OculusXRHMDPrivate.h" +#include "OculusXRHMD.h" + +FOculusXRBodyJoint::FOculusXRBodyJoint() + : LocationFlags(0) + , bIsValid(false) + , Orientation(FRotator::ZeroRotator) + , Position(FVector::ZeroVector) +{ +} + +FOculusXRBodyState::FOculusXRBodyState() + : IsActive(false) + , Confidence(0) + , SkeletonChangedCount(0) + , Time(0.f) +{ + Joints.SetNum(static_cast<int32>(EOculusXRBoneID::COUNT)); +} + +FOculusXRFaceState::FOculusXRFaceState() + : bIsValid(false) + , bIsEyeFollowingBlendshapesValid(false) + , Time(0.f) +{ + ExpressionWeights.SetNum(static_cast<int32>(EOculusXRFaceExpression::COUNT)); + ExpressionWeightConfidences.SetNum(static_cast<int32>(EOculusXRFaceConfidence::COUNT)); +} + +FOculusXREyeGazeState::FOculusXREyeGazeState() + : Orientation(FRotator::ZeroRotator) + , Position(FVector::ZeroVector) + , Confidence(0.f) + , bIsValid(false) +{ +} + +FOculusXREyeGazesState::FOculusXREyeGazesState() + : Time(0.f) +{ + EyeGazes.SetNum(static_cast<int32>(EOculusXREye::COUNT)); +} diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/IOculusXRMovementModule.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/IOculusXRMovementModule.h new file mode 100644 index 0000000000000000000000000000000000000000..2f7edf4587be4317e74639fdaef40e35d6858e41 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/IOculusXRMovementModule.h @@ -0,0 +1,40 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "Modules/ModuleManager.h" + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IOculusXRMovementModule : 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 IOculusXRMovementModule& Get() + { + return FModuleManager::GetModuleChecked<IOculusXRMovementModule>("OculusXRMovement"); + } + + /** + * 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("OculusXRMovement"); + } +}; diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRBodyTrackingComponent.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRBodyTrackingComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..360b66e91b28d0c836fe2bc27e2b1da3e9d1af01 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRBodyTrackingComponent.h @@ -0,0 +1,74 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Components/PoseableMeshComponent.h" + +#include "OculusXRMovementTypes.h" + +#include "OculusXRBodyTrackingComponent.generated.h" + +UENUM(BlueprintType) +enum class EOculusXRBodyTrackingMode : uint8 +{ + PositionAndRotation, + RotationOnly, + NoTracking +}; + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, DisplayName = "OculusXR Body Tracking Component"), ClassGroup = OculusXRHMD) +class OCULUSXRMOVEMENT_API UOculusXRBodyTrackingComponent : public UPoseableMeshComponent +{ + GENERATED_BODY() +public: + UOculusXRBodyTrackingComponent(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + /** + * Restore all bones to their initial transforms + */ + UFUNCTION(BlueprintCallable, Category = "OculusXR|Movement") + void ResetAllBoneTransforms(); + + /** + * How are the results of body tracking applied to the mesh. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Movement") + EOculusXRBodyTrackingMode BodyTrackingMode; + + /** + * The bone name associated with each bone ID. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OculusXR|Movement") + TMap<EOculusXRBoneID, FName> BoneNames; + + /** + * Do not apply body state to bones if confidence is lower than this value. Confidence is in range [0,1]. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Movement", meta = (ClampMin = "0", ClampMax = "1", UIMin = "0", UIMax = "1")) + float ConfidenceThreshold; + +private: + bool InitializeBodyBones(); + + // One meter in unreal world units. + float WorldToMeters; + + // The index of each mapped bone after the discovery and association of bone names. + TMap<EOculusXRBoneID, int32> MappedBoneIndices; + + // Saved body state. + FOculusXRBodyState BodyState; + + // Stop the tracker just once. + static int TrackingInstanceCount; +}; diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXREyeTrackingComponent.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXREyeTrackingComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..b979889824db5de3f170c7ce967b541aaf55aed5 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXREyeTrackingComponent.h @@ -0,0 +1,104 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Components/ActorComponent.h" +#include "Components/PoseableMeshComponent.h" + +#include "OculusXRMovementTypes.h" + +#include "OculusXREyeTrackingComponent.generated.h" + +struct FOculusXREyeTrackingData +{ +public: + FOculusXREyeTrackingData() + : EyeIsMapped(false) + , MappedBoneName(NAME_None) + { + } + + bool EyeIsMapped; + FName MappedBoneName; + FQuat InitialRotation; +}; + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, DisplayName = "OculusXR Eye Tracking Component"), ClassGroup = OculusXRHMD) +class OCULUSXRMOVEMENT_API UOculusXREyeTrackingComponent : public UActorComponent +{ + GENERATED_BODY() +public: + UOculusXREyeTrackingComponent(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + /** + * Reset the rotation values of the eyes to their initial rotation + */ + UFUNCTION(BlueprintCallable, Category = "Oculus|Movement") + void ClearRotationValues(); + + /** + * The name of the poseable mesh component that this component targets for eyes glazes movement. + * This must be the name of a component on this actor. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OculusXR|Movement") + FName TargetMeshComponentName; + + /** + * The map of eye to mesh bone that this component supports. + * Names are validated on (@see BeginPlay) so only valid bone names will be targeted. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OculusXR|Movement") + TMap<EOculusXREye, FName> EyeToBone; + + /** + * Update the target mesh position when eye state changes + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Movement") + bool bUpdatePosition; + + /** + * Update the target mesh rotation when eye state changes + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Movement") + bool bUpdateRotation; + + /** + * Do not accept an eye gaze state if confidence is lower than this value. Confidence is in range [0,1]. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Movement") + float ConfidenceThreshold; + + /** + * Bypass eye gaze state validity. + * + * @Note: It doesn't check the confidence (@see ConfidenceThreshold). The eye gaze state can be marked as invalid. This flag bypass that state flag. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Movement") + bool bAcceptInvalid; + +private: + bool InitializeEyes(); + + // One meter in unreal world units. + float WorldToMeters; + + // Per eye, eye tracking data + TStaticArray<FOculusXREyeTrackingData, static_cast<uint32>(EOculusXREye::COUNT)> PerEyeData; + + // The mesh component targeted for eyes + UPROPERTY() + UPoseableMeshComponent* TargetPoseableMeshComponent; + + // Stop the tracker just once. + static int TrackingInstanceCount; +}; diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRFaceTrackingComponent.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRFaceTrackingComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..fd254471ecf5e1b3d1a6bff66a4c17117caf5acb --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRFaceTrackingComponent.h @@ -0,0 +1,98 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "Components/SkeletalMeshComponent.h" + +#include "OculusXRMorphTargetsController.h" +#include "OculusXRMovementTypes.h" + +#include "OculusXRFaceTrackingComponent.generated.h" + +UCLASS(Blueprintable, meta = (BlueprintSpawnableComponent, DisplayName = "OculusXR Face Tracking Component"), ClassGroup = OculusXRHMD) +class OCULUSXRMOVEMENT_API UOculusXRFaceTrackingComponent : public UActorComponent +{ + GENERATED_BODY() +public: + UOculusXRFaceTrackingComponent(); + + virtual void BeginPlay() override; + virtual void EndPlay(const EEndPlayReason::Type EndPlayReason) override; + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + + /** + * Set face expression value with expression key and value(0-1). + * + * @param Expression : The expression key that will be modified. + * @param Value : The new value to assign to the expression, 0 will remove all changes. + */ + UFUNCTION(BlueprintCallable, Category = "Components|OculusXRFaceTracking", meta = (UnsafeDuringActorConstruction = "true")) + void SetExpressionValue(EOculusXRFaceExpression Expression, float Value); + + /** + * Get a face expression value given an expression key. + * + * @param Expression : The expression key that will be queried. + */ + UFUNCTION(BlueprintCallable, Category = "Components|OculusXRFaceTracking") + float GetExpressionValue(EOculusXRFaceExpression Expression) const; + + /** + * Clears all face expression values. + */ + UFUNCTION(BlueprintCallable, Category = "Components|OculusXRFaceTracking") + void ClearExpressionValues(); + + /** + * The name of the skinned mesh component that this component targets for facial expression. + * This must be the name of a component on this actor. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OculusXR|Movement") + FName TargetMeshComponentName; + + /** + * If the face data is invalid for at least this or longer than this time then all face blendshapes/morph targets are reset to zero. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OculusXR|Movement") + float InvalidFaceDataResetTime; + + /** + * The list of expressions that this component supports. + * Names are validated on startup so only valid morph targets on the skeletal mesh will be targeted. + */ + UPROPERTY(EditAnywhere, BlueprintReadOnly, Category = "OculusXR|Movement") + TMap<EOculusXRFaceExpression, FName> ExpressionNames; + + /** + * This flag determines if the face should be updated or not during the components tick. + */ + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "Oculus|Movement") + bool bUpdateFace; + +private: + bool InitializeFaceTracking(); + + // The mesh component targeted for expressions + UPROPERTY() + USkinnedMeshComponent* TargetMeshComponent; + + // Which mapped expressions are valid + TStaticArray<bool, static_cast<uint32>(EOculusXRFaceExpression::COUNT)> ExpressionValid; + + // Morph targets controller + FOculusXRMorphTargetsController MorphTargets; + + FOculusXRFaceState FaceState; + + // Timer that counts up until we reset morph curves if we've failed to get face state + float InvalidFaceStateTimer; + + // Stop the tracker just once. + static int TrackingInstanceCount; +}; diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMorphTargetsController.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMorphTargetsController.h new file mode 100644 index 0000000000000000000000000000000000000000..8eba71ef471b5e9cc5ebbbc80cccabc3a6c2e980 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMorphTargetsController.h @@ -0,0 +1,41 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "Components/SkinnedMeshComponent.h" + +/* +* Struct that allows applying morph targets data to an arbitrary skinned mesh component +* instead of relying on the skeletal mesh component. +* +* Usage - In a tick method of your choosing: +* 1) ResetMorphTargetCurves(Component) at the start of the update. +* 2) SetMorphTarget(...) as many times as needed based on your data set. +* 3) ApplyMorphTargets(Component) at the end of the update to apply the morph targets to the anim runtime. +*/ +struct OCULUSXRMOVEMENT_API FOculusXRMorphTargetsController +{ +public: + // Clears active morph targets + void ResetMorphTargetCurves(USkinnedMeshComponent* TargetMeshComponent); + + // Will apply morph target data to the underlying runtime skeletal mesh + void ApplyMorphTargets(USkinnedMeshComponent* TargetMeshComponent); + + // Sets a specific morph target value + void SetMorphTarget(FName MorphTargetName, float Value); + + // Gets a specific morph target value + float GetMorphTarget(FName MorphTargetName) const; + + // Clears all morph target curves data + void ClearMorphTargets(); + + // List of morph targets on this controller + TMap<FName, float> MorphTargetCurves; +}; diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovement.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovement.h new file mode 100644 index 0000000000000000000000000000000000000000..d263cc8d97735b50233dfc3ce642d4b5232f72a7 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovement.h @@ -0,0 +1,31 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "OculusXRMovementTypes.h" + +struct OCULUSXRMOVEMENT_API OculusXRMovement +{ + static bool GetBodyState(FOculusXRBodyState& outOculusXRBodyState, float WorldToMeters = 100.0f); + static bool IsBodyTrackingEnabled(); + static bool IsBodyTrackingSupported(); + static bool StartBodyTracking(); + static bool StopBodyTracking(); + + static bool GetFaceState(FOculusXRFaceState& outOculusXRFaceState); + static bool IsFaceTrackingEnabled(); + static bool IsFaceTrackingSupported(); + static bool StartFaceTracking(); + static bool StopFaceTracking(); + + static bool GetEyeGazesState(FOculusXREyeGazesState& outOculusXREyeGazesState, float WorldToMeters = 100.0f); + static bool IsEyeTrackingEnabled(); + static bool IsEyeTrackingSupported(); + static bool StartEyeTracking(); + static bool StopEyeTracking(); +}; diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementFunctionLibrary.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementFunctionLibrary.h new file mode 100644 index 0000000000000000000000000000000000000000..a2e249aed28c7f9829f1aa8d7f15f6ddfe736185 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementFunctionLibrary.h @@ -0,0 +1,63 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "OculusXRMovementTypes.h" + +#include "OculusXRMovementFunctionLibrary.generated.h" + +UCLASS() +class OCULUSXRMOVEMENT_API UOculusXRMovementFunctionLibrary : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() +public: + UFUNCTION(BlueprintPure, Category = "OculusXR|Body") + static bool TryGetBodyState(FOculusXRBodyState& outBodyState, float WorldToMeters = 100.0f); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Body") + static bool IsBodyTrackingEnabled(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Body") + static bool IsBodyTrackingSupported(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Body") + static bool StartBodyTracking(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Body") + static bool StopBodyTracking(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Face") + static bool TryGetFaceState(FOculusXRFaceState& outFaceState); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Face") + static bool IsFaceTrackingEnabled(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Face") + static bool IsFaceTrackingSupported(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Face") + static bool StartFaceTracking(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Face") + static bool StopFaceTracking(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Eyes") + static bool TryGetEyeGazesState(FOculusXREyeGazesState& outEyeGazesState, float WorldToMeters = 100.0f); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Eyes") + static bool IsEyeTrackingEnabled(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Eyes") + static bool IsEyeTrackingSupported(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Eyes") + static bool StartEyeTracking(); + + UFUNCTION(BlueprintPure, Category = "OculusXR|Eyes") + static bool StopEyeTracking(); +}; diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementHelpers.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementHelpers.h new file mode 100644 index 0000000000000000000000000000000000000000..a0768d431f1cc8ec0184ff784c352fd38c637e00 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementHelpers.h @@ -0,0 +1,29 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +namespace OculusXRUtility +{ + template <typename T> + T* FindComponentByName(AActor* Actor, const FName& ComponentName) + { + if (IsValid(Actor) && (ComponentName != NAME_None)) + { + TArray<T*> ComponentsOfType; + Actor->GetComponents<T>(ComponentsOfType); + T** FoundComponent = ComponentsOfType.FindByPredicate([Name = ComponentName.ToString()](T* Component) { return Component->GetName().Equals(Name); }); + + if (FoundComponent != nullptr) + { + return *FoundComponent; + } + } + + return nullptr; + } +} // namespace OculusXRUtility diff --git a/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementTypes.h b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementTypes.h new file mode 100644 index 0000000000000000000000000000000000000000..79067a7c06b852c6c787e78c926f325aff247f7c --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRMovement/Public/OculusXRMovementTypes.h @@ -0,0 +1,273 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" + +#include "OculusXRMovementTypes.generated.h" + +UENUM(BlueprintType) +enum class EOculusXRBoneID : uint8 +{ + BodyRoot = 0, + BodyHips = 1, + BodySpineLower = 2, + BodySpineMiddle = 3, + BodySpineUpper = 4, + BodyChest = 5, + BodyNeck = 6, + BodyHead = 7, + BodyLeftShoulder = 8, + BodyLeftScapula = 9, + BodyLeftArmUpper = 10, + BodyLeftArmLower = 11, + BodyLeftHandWristTwist = 12, + BodyRightShoulder = 13, + BodyRightScapula = 14, + BodyRightArmUpper = 15, + BodyRightArmLower = 16, + BodyRightHandWristTwist = 17, + BodyLeftHandPalm = 18, + BodyLeftHandWrist = 19, + BodyLeftHandThumbMetacarpal = 20, + BodyLeftHandThumbProximal = 21, + BodyLeftHandThumbDistal = 22, + BodyLeftHandThumbTip = 23, + BodyLeftHandIndexMetacarpal = 24, + BodyLeftHandIndexProximal = 25, + BodyLeftHandIndexIntermediate = 26, + BodyLeftHandIndexDistal = 27, + BodyLeftHandIndexTip = 28, + BodyLeftHandMiddleMetacarpal = 29, + BodyLeftHandMiddleProximal = 30, + BodyLeftHandMiddleIntermediate = 31, + BodyLeftHandMiddleDistal = 32, + BodyLeftHandMiddleTip = 33, + BodyLeftHandRingMetacarpal = 34, + BodyLeftHandRingProximal = 35, + BodyLeftHandRingIntermediate = 36, + BodyLeftHandRingDistal = 37, + BodyLeftHandRingTip = 38, + BodyLeftHandLittleMetacarpal = 39, + BodyLeftHandLittleProximal = 40, + BodyLeftHandLittleIntermediate = 41, + BodyLeftHandLittleDistal = 42, + BodyLeftHandLittleTip = 43, + BodyRightHandPalm = 44, + BodyRightHandWrist = 45, + BodyRightHandThumbMetacarpal = 46, + BodyRightHandThumbProximal = 47, + BodyRightHandThumbDistal = 48, + BodyRightHandThumbTip = 49, + BodyRightHandIndexMetacarpal = 50, + BodyRightHandIndexProximal = 51, + BodyRightHandIndexIntermediate = 52, + BodyRightHandIndexDistal = 53, + BodyRightHandIndexTip = 54, + BodyRightHandMiddleMetacarpal = 55, + BodyRightHandMiddleProximal = 56, + BodyRightHandMiddleIntermediate = 57, + BodyRightHandMiddleDistal = 58, + BodyRightHandMiddleTip = 59, + BodyRightHandRingMetacarpal = 60, + BodyRightHandRingProximal = 61, + BodyRightHandRingIntermediate = 62, + BodyRightHandRingDistal = 63, + BodyRightHandRingTip = 64, + BodyRightHandLittleMetacarpal = 65, + BodyRightHandLittleProximal = 66, + BodyRightHandLittleIntermediate = 67, + BodyRightHandLittleDistal = 68, + BodyRightHandLittleTip = 69, + COUNT = 70, +}; + +USTRUCT(BlueprintType) +struct OCULUSXRMOVEMENT_API FOculusXRBodyJoint +{ + GENERATED_BODY() +public: + FOculusXRBodyJoint(); + + uint64 LocationFlags; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + bool bIsValid; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + FRotator Orientation; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + FVector Position; +}; + +USTRUCT(BlueprintType) +struct OCULUSXRMOVEMENT_API FOculusXRBodyState +{ + GENERATED_BODY() +public: + FOculusXRBodyState(); + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + bool IsActive; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + float Confidence; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + int SkeletonChangedCount; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + float Time; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + TArray<FOculusXRBodyJoint> Joints; +}; + +UENUM(BlueprintType) +enum class EOculusXRFaceExpression : uint8 +{ + // Removed invalid to make this supported as a uint8 enum class + BrowLowererL = 0, + BrowLowererR = 1, + CheekPuffL = 2, + CheekPuffR = 3, + CheekRaiserL = 4, + CheekRaiserR = 5, + CheekSuckL = 6, + CheekSuckR = 7, + ChinRaiserB = 8, + ChinRaiserT = 9, + DimplerL = 10, + DimplerR = 11, + EyesClosedL = 12, + EyesClosedR = 13, + EyesLookDownL = 14, + EyesLookDownR = 15, + EyesLookLeftL = 16, + EyesLookLeftR = 17, + EyesLookRightL = 18, + EyesLookRightR = 19, + EyesLookUpL = 20, + EyesLookUpR = 21, + InnerBrowRaiserL = 22, + InnerBrowRaiserR = 23, + JawDrop = 24, + JawSidewaysLeft = 25, + JawSidewaysRight = 26, + JawThrust = 27, + LidTightenerL = 28, + LidTightenerR = 29, + LipCornerDepressorL = 30, + LipCornerDepressorR = 31, + LipCornerPullerL = 32, + LipCornerPullerR = 33, + LipFunnelerLB = 34, + LipFunnelerLT = 35, + LipFunnelerRB = 36, + LipFunnelerRT = 37, + LipPressorL = 38, + LipPressorR = 39, + LipPuckerL = 40, + LipPuckerR = 41, + LipStretcherL = 42, + LipStretcherR = 43, + LipSuckLB = 44, + LipSuckLT = 45, + LipSuckRB = 46, + LipSuckRT = 47, + LipTightenerL = 48, + LipTightenerR = 49, + LipsToward = 50, + LowerLipDepressorL = 51, + LowerLipDepressorR = 52, + MouthLeft = 53, + MouthRight = 54, + NoseWrinklerL = 55, + NoseWrinklerR = 56, + OuterBrowRaiserL = 57, + OuterBrowRaiserR = 58, + UpperLidRaiserL = 59, + UpperLidRaiserR = 60, + UpperLipRaiserL = 61, + UpperLipRaiserR = 62, + COUNT = 63, +}; + +UENUM(BlueprintType) +enum class EOculusXRFaceConfidence : uint8 +{ + Lower = 0, + Upper = 1, + COUNT = 2, +}; + +USTRUCT(BlueprintType) +struct OCULUSXRMOVEMENT_API FOculusXRFaceState +{ + GENERATED_BODY() +public: + FOculusXRFaceState(); + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + TArray<float> ExpressionWeights; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + TArray<float> ExpressionWeightConfidences; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + bool bIsValid; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + bool bIsEyeFollowingBlendshapesValid; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + float Time; +}; + +UENUM(BlueprintType) +enum class EOculusXREye : uint8 +{ + Left = 0, + Right = 1, + COUNT = 2, +}; + +USTRUCT(BlueprintType) +struct OCULUSXRMOVEMENT_API FOculusXREyeGazeState +{ + GENERATED_BODY() +public: + FOculusXREyeGazeState(); + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + FRotator Orientation; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + FVector Position; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + float Confidence; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + bool bIsValid; +}; + +USTRUCT(BlueprintType) +struct OCULUSXRMOVEMENT_API FOculusXREyeGazesState +{ + GENERATED_BODY() +public: + FOculusXREyeGazesState(); + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + TArray<FOculusXREyeGazeState> EyeGazes; + + UPROPERTY(BlueprintReadOnly, Category = "OculusXR|Movement") + float Time; +}; diff --git a/Plugins/OculusXR/Source/OculusXROpenXRHMD/OculusXROpenXRHMD.Build.cs b/Plugins/OculusXR/Source/OculusXROpenXRHMD/OculusXROpenXRHMD.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..3e696b26ea37ccddd5b0c22a7dc7594562c13e83 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXROpenXRHMD/OculusXROpenXRHMD.Build.cs @@ -0,0 +1,116 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using System; +using System.IO; + +namespace UnrealBuildTool.Rules +{ + public class OculusXROpenXRHMD : ModuleRules + { + public OculusXROpenXRHMD(ReadOnlyTargetRules Target) : base(Target) + { + var EngineDir = Path.GetFullPath(Target.RelativeEnginePath); + + PublicIncludePaths.AddRange( + new string[] { + // Relative to Engine\Plugins\OculusXR\Source\OculusOpenXR\Source + Path.Combine(EngineDir, "Plugins/Runtime/OpenXR/Source/OpenXRHMD/Private"), + Path.Combine(EngineDir, "Source/Runtime/Renderer/Private"), + Path.Combine(EngineDir, "Source/Runtime/OpenGLDrv/Private"), + Path.Combine(EngineDir, "Source/Runtime/Engine/Classes/Components"), + Path.Combine(EngineDir, "Source/Runtime/Engine/Classes/Kismet"), + }); + + PublicIncludePathModuleNames.AddRange( + new string[] { + "Launch", + "OpenXRHMD", + }); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "RHI", + "RHICore", + "RenderCore", + "Renderer", + "Slate", + "SlateCore", + "ImageWrapper", + "MediaAssets", + "Analytics", + "OpenGLDrv", + "VulkanRHI", + "HeadMountedDisplay", + "OpenXR", + "OculusOpenXRLoader", + "Projects", + }); + PublicDependencyModuleNames.AddRange( + new string[] + { + "OpenXRHMD", + }); + + if (Target.bBuildEditor == true) + { + PrivateDependencyModuleNames.Add("UnrealEd"); + } + + AddEngineThirdPartyPrivateStaticDependencies(Target, "OpenGL"); + + if (Target.Platform == UnrealTargetPlatform.Win64) + { + // D3D + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "D3D11RHI", + "D3D12RHI", + }); + + + PublicIncludePaths.AddRange( + new string[] + { + Path.Combine(EngineDir, "Source/Runtime/Windows/D3D11RHI/Private"), + Path.Combine(EngineDir, "Source/Runtime/Windows/D3D11RHI/Private/Windows"), + Path.Combine(EngineDir, "Source/Runtime/D3D12RHI/Private"), + Path.Combine(EngineDir, "Source/Runtime/D3D12RHI/Private/Windows"), + }); + + AddEngineThirdPartyPrivateStaticDependencies(Target, "DX11"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "DX12"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "NVAPI"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "DX11Audio"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "DirectSound"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "NVAftermath"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "IntelMetricsDiscovery"); + AddEngineThirdPartyPrivateStaticDependencies(Target, "IntelExtensionsFramework"); + } + + // Vulkan + { + AddEngineThirdPartyPrivateStaticDependencies(Target, "Vulkan"); + } + } + else if (Target.Platform == UnrealTargetPlatform.Android) + { + PrivateIncludePaths.AddRange( + new string[] + { + }); + + // Vulkan + { + AddEngineThirdPartyPrivateStaticDependencies(Target, "Vulkan"); + } + } + } + } +} diff --git a/Plugins/OculusXR/Source/OculusXROpenXRHMD/Private/OculusXROpenXRHMD.cpp b/Plugins/OculusXR/Source/OculusXROpenXRHMD/Private/OculusXROpenXRHMD.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e0e376143a2527768bf7bc16ea3b8e6056cdaef6 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXROpenXRHMD/Private/OculusXROpenXRHMD.cpp @@ -0,0 +1,137 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXROpenXRHMD.h" +#include "OpenXRCore.h" +#include "OpenXRPlatformRHI.h" +#include "DefaultSpectatorScreenController.h" +#include "Modules/ModuleManager.h" + +#if PLATFORM_ANDROID +//#include <openxr_oculus.h> +#include <dlfcn.h> +#endif //PLATFORM_ANDROID + +DEFINE_LOG_CATEGORY(LogOculusOpenXRPlugin); + +bool FOculusXROpenXRHMD::IsStandaloneStereoOnlyDevice() +{ +#if PLATFORM_ANDROID + const bool bIsStandaloneStereoDevice = FAndroidMisc::GetDeviceMake() == FString("Oculus"); +#else + const bool bIsStandaloneStereoDevice = false; +#endif + return bIsStandaloneStereoDevice; +} + +bool FOculusXROpenXRHMD::GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) +{ + return true; +} + +bool FOculusXROpenXRHMD::GetInteractionProfile(XrInstance InInstance, FString& OutKeyPrefix, XrPath& OutPath, bool& OutHasHaptics) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR GetInteractionProfile")); + return false; // if you return true, make sure OutPath and OutHasHaptics are initialized +} + +bool FOculusXROpenXRHMD::GetSpectatorScreenController(FHeadMountedDisplayBase* InHMDBase, TUniquePtr<FDefaultSpectatorScreenController>& OutSpectatorScreenController) +{ +#if PLATFORM_ANDROID + OutSpectatorScreenController = nullptr; + return true; +#else // PLATFORM_ANDROID + OutSpectatorScreenController = MakeUnique<FDefaultSpectatorScreenController>(InHMDBase); + return false; +#endif // PLATFORM_ANDROID +} + +void FOculusXROpenXRHMD::AddActions(XrInstance Instance, TFunction<XrAction(XrActionType InActionType, const FName& InName, const TArray<XrPath>& InSubactionPaths)> AddAction) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR AddActions")); + return; +} + +void FOculusXROpenXRHMD::OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader) +{ + return; +} + +const void* FOculusXROpenXRHMD::OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnCreateInstance")); + return InNext; +} + +const void* FOculusXROpenXRHMD::OnGetSystem(XrInstance InInstance, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnGetSystem")); + return InNext; +} + +const void* FOculusXROpenXRHMD::OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnCreateSession")); +#if PLATFORM_ANDROID + if (GRHISupportsRHIThread && GIsThreadedRendering && GUseRHIThread_InternalUseOnly) + { + SetRHIThreadEnabled(false, false); + } +#endif // PLATFORM_ANDROID + return InNext; +} + +const void* FOculusXROpenXRHMD::OnBeginSession(XrSession InSession, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnBeginSession")); + return InNext; +} + +const void* FOculusXROpenXRHMD::OnBeginFrame(XrSession InSession, XrTime DisplayTime, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnBeginFrame")); + return InNext; +} + +const void* FOculusXROpenXRHMD::OnBeginProjectionView(XrSession InSession, int32 InLayerIndex, int32 InViewIndex, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnBeginProjectionView")); + return InNext; +} + +const void* FOculusXROpenXRHMD::OnBeginDepthInfo(XrSession InSession, int32 InLayerIndex, int32 InViewIndex, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnBeginDepthInfo")); + return InNext; +} + +const void* FOculusXROpenXRHMD::OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnEndProjectionLayer")); + + // XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT is required right now because the Oculus mobile runtime blends using alpha otherwise, + // and we don't have proper inverse alpha support in OpenXR yet (once OpenXR supports inverse alpha, or we change the runtime behavior, remove this) + OutFlags |= XR_COMPOSITION_LAYER_CORRECT_CHROMATIC_ABERRATION_BIT; + OutFlags |= XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT; + + return InNext; +} + +const void* FOculusXROpenXRHMD::OnEndFrame(XrSession InSession, XrTime DisplayTime, const TArray<XrSwapchainSubImage> InColorImages, const TArray<XrSwapchainSubImage> InDepthImages, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnEndFrame")); + return InNext; +} + +const void* FOculusXROpenXRHMD::OnSyncActions(XrSession InSession, const void* InNext) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR OnSyncActions")); + return InNext; +} + +void FOculusXROpenXRHMD::PostSyncActions(XrSession InSession) +{ + //UE_LOG(LogOculusOpenXRPlugin, Log, TEXT("Oculus OpenXR PostSyncActions")); + return; +} + +IMPLEMENT_MODULE(FOculusXROpenXRHMD, OculusXROpenXRHMD) diff --git a/Plugins/OculusXR/Source/OculusXROpenXRHMD/Private/OculusXROpenXRHMD.h b/Plugins/OculusXR/Source/OculusXROpenXRHMD/Private/OculusXROpenXRHMD.h new file mode 100644 index 0000000000000000000000000000000000000000..1aeefc31c5e11803975d323a3b26db64611725d5 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXROpenXRHMD/Private/OculusXROpenXRHMD.h @@ -0,0 +1,53 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "CoreMinimal.h" +#include "IOculusXROpenXRHMDPlugin.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogOculusOpenXRPlugin, Log, All); + +class FOculusXROpenXRHMD : public IOculusXROpenXRHMDPlugin +{ +private: + void *LoaderHandle; + +public: + FOculusXROpenXRHMD() + : LoaderHandle(nullptr) + { } + + virtual ~FOculusXROpenXRHMD() + { } + + virtual void StartupModule() override + { + RegisterOpenXRExtensionModularFeature(); + } + + virtual void ShutdownModule() override + { + if (LoaderHandle) + { + FPlatformProcess::FreeDllHandle(LoaderHandle); + LoaderHandle = nullptr; + } + } + + virtual bool IsStandaloneStereoOnlyDevice() override; + virtual bool GetRequiredExtensions(TArray<const ANSICHAR*>& OutExtensions) override; + virtual bool GetInteractionProfile(XrInstance InInstance, FString& OutKeyPrefix, XrPath& OutPath, bool& OutHasHaptics) override; + virtual bool GetSpectatorScreenController(FHeadMountedDisplayBase* InHMDBase, TUniquePtr<FDefaultSpectatorScreenController>& OutSpectatorScreenController) override; + virtual void AddActions(XrInstance Instance, TFunction<XrAction(XrActionType InActionType, const FName& InName, const TArray<XrPath>& InSubactionPaths)> AddAction) override; + virtual void OnEvent(XrSession InSession, const XrEventDataBaseHeader* InHeader) override; + virtual const void* OnCreateInstance(class IOpenXRHMDModule* InModule, const void* InNext) override; + virtual const void* OnGetSystem(XrInstance InInstance, const void* InNext) override; + virtual const void* OnCreateSession(XrInstance InInstance, XrSystemId InSystem, const void* InNext) override; + virtual const void* OnBeginSession(XrSession InSession, const void* InNext) override; + virtual const void* OnBeginFrame(XrSession InSession, XrTime DisplayTime, const void* InNext) override; + virtual const void* OnBeginProjectionView(XrSession InSession, int32 InLayerIndex, int32 InViewIndex, const void* InNext) override; + virtual const void* OnBeginDepthInfo(XrSession InSession, int32 InLayerIndex, int32 InViewIndex, const void* InNext) override; + virtual const void* OnEndProjectionLayer(XrSession InSession, int32 InLayerIndex, const void* InNext, XrCompositionLayerFlags& OutFlags) override; + virtual const void* OnEndFrame(XrSession InSession, XrTime DisplayTime, const TArray<XrSwapchainSubImage> InColorImages, const TArray<XrSwapchainSubImage> InDepthImages, const void* InNext) override; + virtual const void* OnSyncActions(XrSession InSession, const void* InNext) override; + virtual void PostSyncActions(XrSession InSession) override; +}; diff --git a/Plugins/OculusXR/Source/OculusXROpenXRHMD/Public/IOculusXROpenXRHMDPlugin.h b/Plugins/OculusXR/Source/OculusXROpenXRHMD/Public/IOculusXROpenXRHMDPlugin.h new file mode 100644 index 0000000000000000000000000000000000000000..f4cc40d6e372499897ddbb39a2cb6657697ff793 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXROpenXRHMD/Public/IOculusXROpenXRHMDPlugin.h @@ -0,0 +1,9 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "IOpenXRExtensionPlugin.h" + +class IOculusXROpenXRHMDPlugin : public IOpenXRExtensionPlugin, public IModuleInterface +{ + +}; \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRScene/OculusXRScene.Build.cs b/Plugins/OculusXR/Source/OculusXRScene/OculusXRScene.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..42e057f91aa5ab9ae31268ab5161c666166d85f8 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRScene/OculusXRScene.Build.cs @@ -0,0 +1,33 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +namespace UnrealBuildTool.Rules +{ + public class OculusXRScene : ModuleRules + { + public OculusXRScene(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "OculusXRHMD", + "OculusXRAnchors", + "OVRPluginXR", + }); + + PrivateIncludePaths.AddRange( + new string[] { + // Relative to Engine\Plugins\Runtime\Oculus\OculusVR\Source + "OculusXRHMD/Private", + "OculusXRAnchors/Private" + }); + + PublicIncludePaths.AddRange( + new string[] { + "Runtime/Engine/Classes/Components", + }); + } + } +} diff --git a/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneActor.cpp b/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneActor.cpp new file mode 100644 index 0000000000000000000000000000000000000000..37e1a84a5d3f6ee1bdaab9c45162d10897742553 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneActor.cpp @@ -0,0 +1,642 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRSceneActor.h" +#include "OculusXRSceneModule.h" +#include "OculusXRHMDModule.h" +#include "OculusXRAnchorManager.h" +#include "OculusXRDelegates.h" +#include "Components/StaticMeshComponent.h" +#include "Engine/AssetManager.h" +#include "Engine/StaticMesh.h" +#include "Engine/StaticMeshActor.h" +#include "Engine/World.h" +#include "GameFramework/WorldSettings.h" + +#define LOCTEXT_NAMESPACE "OculusXRSceneActor" + +////////////////////////////////////////////////////////////////////////// +// ASceneActor + +AOculusXRSceneActor::AOculusXRSceneActor(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) +{ + ResetStates(); + + // Create required components + RoomLayoutManagerComponent = CreateDefaultSubobject<UOculusXRRoomLayoutManagerComponent>(TEXT("OculusXRRoomLayoutManagerComponent")); + + // Following are the semantic labels we want to support default properties for. User can always add new ones through the properties panel if needed. + const FString defaultSemanticClassification[] = { + TEXT("WALL_FACE"), + TEXT("CEILING"), + TEXT("FLOOR"), + TEXT("COUCH"), + TEXT("DESK"), + TEXT("DOOR_FRAME"), + TEXT("WINDOW_FRAME"), + TEXT("OTHER") + }; + + FOculusXRSpawnedSceneAnchorProperties spawnedAnchorProps; + + // Populate default UPROPERTY for the "ScenePlaneSpawnedSceneAnchorProperties" member + spawnedAnchorProps.StaticMesh = TSoftObjectPtr<UStaticMesh>(FSoftObjectPath("/OculusVR/Meshes/ScenePlane.ScenePlane")); + for (int32 i = 0; i < sizeof(defaultSemanticClassification) / sizeof(defaultSemanticClassification[0]); ++i) + { + FOculusXRSpawnedSceneAnchorProperties& props = ScenePlaneSpawnedSceneAnchorProperties.Add(defaultSemanticClassification[i], spawnedAnchorProps); + + // Orientation constraints + if (defaultSemanticClassification[i] == "CEILING" || + defaultSemanticClassification[i] == "FLOOR" || + defaultSemanticClassification[i] == "COUCH" || + defaultSemanticClassification[i] == "DESK" || + defaultSemanticClassification[i] == "DOOR_FRAME" || + defaultSemanticClassification[i] == "WINDOW_FRAME" || + defaultSemanticClassification[i] == "OTHER") + { + props.ForceParallelToFloor = true; + } + } + + // Populate default UPROPERTY for the "SceneVolumeSpawnedSceneAnchorProperties" member + // For the time being, only "OTHER" semantic label is used for volume scene anchors. + spawnedAnchorProps.StaticMesh = TSoftObjectPtr<UStaticMesh>(FSoftObjectPath("/OculusVR/Meshes/SceneVolume.SceneVolume")); + FOculusXRSpawnedSceneAnchorProperties& props = SceneVolumeSpawnedSceneAnchorProperties.Add(TEXT("OTHER"), spawnedAnchorProps); + props.ForceParallelToFloor = true; +} + +void AOculusXRSceneActor::ResetStates() +{ + bCaptureFlowWasLaunched = false; + ClearScene(); +} + +void AOculusXRSceneActor::BeginPlay() +{ + Super::BeginPlay(); + + // Create a scene component as root so we can attach spawned actors to it + USceneComponent* rootSceneComponent = NewObject<USceneComponent>(this, USceneComponent::StaticClass()); + rootSceneComponent->SetMobility(EComponentMobility::Static); + rootSceneComponent->RegisterComponent(); + SetRootComponent(rootSceneComponent); + + // Register delegates + RoomLayoutManagerComponent->OculusXRRoomLayoutSceneCaptureCompleteNative.AddUObject(this, &AOculusXRSceneActor::SceneCaptureComplete_Handler); + + // Make an initial request to query for the room layout if bPopulateSceneOnBeginPlay was set to true + if (bPopulateSceneOnBeginPlay) + { + PopulateScene(); + } +} + +void AOculusXRSceneActor::EndPlay(EEndPlayReason::Type Reason) +{ + // Unregister delegates + RoomLayoutManagerComponent->OculusXRRoomLayoutSceneCaptureCompleteNative.RemoveAll(this); + + // Calling ResetStates will reset member variables to their default values (including the request IDs). + ResetStates(); + + Super::EndPlay(Reason); +} + +void AOculusXRSceneActor::Tick(float DeltaTime) +{ + Super::Tick(DeltaTime); +} + +bool AOculusXRSceneActor::QuerySpatialAnchors(const bool bRoomLayoutOnly) +{ + bool bResult = false; + + FOculusXRSpaceQueryInfo queryInfo; + queryInfo.MaxQuerySpaces = MaxQueries; + queryInfo.FilterType = EOculusXRSpaceQueryFilterType::FilterByComponentType; + + if (bRoomLayoutOnly) + { + queryInfo.ComponentFilter.Add(EOculusXRSpaceComponentType::RoomLayout); + bResult = OculusXRAnchors::FOculusXRAnchors::QueryAnchorsAdvanced(queryInfo, FOculusXRAnchorQueryDelegate::CreateUObject(this, &AOculusXRSceneActor::AnchorQueryComplete_Handler)); + } + else + { + queryInfo.ComponentFilter.Add(EOculusXRSpaceComponentType::ScenePlane); + bResult = OculusXRAnchors::FOculusXRAnchors::QueryAnchorsAdvanced(queryInfo, FOculusXRAnchorQueryDelegate::CreateUObject(this, &AOculusXRSceneActor::AnchorQueryComplete_Handler)); + + queryInfo.ComponentFilter.Empty(); + queryInfo.ComponentFilter.Add(EOculusXRSpaceComponentType::SceneVolume); + bResult &= OculusXRAnchors::FOculusXRAnchors::QueryAnchorsAdvanced(queryInfo, FOculusXRAnchorQueryDelegate::CreateUObject(this, &AOculusXRSceneActor::AnchorQueryComplete_Handler)); + } + + return bResult; +} + +bool AOculusXRSceneActor::IsValidUuid(const FOculusXRUUID& Uuid) +{ + return Uuid.UUIDBytes != nullptr; +} + +void AOculusXRSceneActor::LaunchCaptureFlow() +{ + UE_LOG(LogOculusXRScene, Error, TEXT("Launch capture flow")); + + if (RoomLayoutManagerComponent) + { + UE_LOG(LogOculusXRScene, Error, TEXT("Launch capture flow -- RoomLayoutManagerComponent")); + + const bool bResult = RoomLayoutManagerComponent->LaunchCaptureFlow(); + if (!bResult) + { + UE_LOG(LogOculusXRScene, Error, TEXT("LaunchCaptureFlow() failed!")); + } + } +} + +void AOculusXRSceneActor::LaunchCaptureFlowIfNeeded() +{ +#if WITH_EDITOR + UE_LOG(LogOculusXRScene, Display, TEXT("Scene Capture does not work over Link. Please capture a scene with the HMD in standalone mode, then access the scene model over Link.")); +#else + // Depending on LauchCaptureFlowWhenMissingScene, we might not want to launch Capture Flow + if (LauchCaptureFlowWhenMissingScene != EOculusXRLaunchCaptureFlowWhenMissingScene::NEVER) + { + if (LauchCaptureFlowWhenMissingScene == EOculusXRLaunchCaptureFlowWhenMissingScene::ALWAYS || + (!bCaptureFlowWasLaunched && LauchCaptureFlowWhenMissingScene == EOculusXRLaunchCaptureFlowWhenMissingScene::ONCE)) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("Requesting to launch Capture Flow.")); + } + + LaunchCaptureFlow(); + } + } +#endif +} + +bool AOculusXRSceneActor::SpawnSceneAnchor(const FOculusXRUInt64& Space, const FVector& BoundedSize, const TArray<FString>& SemanticClassifications, const EOculusXRSpaceComponentType AnchorComponentType) +{ + if (Space.Value == 0) + { + UE_LOG(LogOculusXRScene, Error, TEXT("AOculusXRSceneActor::SpawnSceneAnchor Invalid Space handle.")); + return false; + } + + if (!(AnchorComponentType == EOculusXRSpaceComponentType::ScenePlane || AnchorComponentType == EOculusXRSpaceComponentType::SceneVolume)) + { + UE_LOG(LogOculusXRScene, Error, TEXT("AOculusXRSceneActor::SpawnSceneAnchor Anchor doesn't have ScenePlane or SceneVolume component active.")); + return false; + } + + if (0 == SemanticClassifications.Num()) + { + UE_LOG(LogOculusXRScene, Error, TEXT("AOculusXRSceneActor::SpawnSceneAnchor No semantic classification found.")); + return false; + } + + TSoftClassPtr<UOculusXRSceneAnchorComponent>* sceneAnchorComponentClassPtrRef = nullptr; + TSoftObjectPtr<UStaticMesh>* staticMeshObjPtrRef = nullptr; + + FOculusXRSpawnedSceneAnchorProperties* foundProperties = (AnchorComponentType == EOculusXRSpaceComponentType::ScenePlane) ? ScenePlaneSpawnedSceneAnchorProperties.Find(SemanticClassifications[0]) : SceneVolumeSpawnedSceneAnchorProperties.Find(SemanticClassifications[0]); + + if (!foundProperties) + { + UE_LOG(LogOculusXRScene, Warning, TEXT("AOculusXRSceneActor::SpawnSceneAnchor Scene object has an unknown semantic label. Will not be spawned.")); + return false; + } + + sceneAnchorComponentClassPtrRef = &foundProperties->ActorComponent; + staticMeshObjPtrRef = &foundProperties->StaticMesh; + + UClass* sceneAnchorComponentInstanceClass = sceneAnchorComponentClassPtrRef->LoadSynchronous(); + if (!sceneAnchorComponentInstanceClass) + { + UE_LOG(LogOculusXRScene, Error, TEXT("AOculusXRSceneActor::SpawnSceneAnchor Scene anchor component class is invalid! Cannot spawn actor to populate the scene.")); + return false; + } + + FActorSpawnParameters actorSpawnParams; + actorSpawnParams.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn; + AStaticMeshActor* newActor = GetWorld()->SpawnActor<AStaticMeshActor>(AStaticMeshActor::StaticClass(), FVector(), FRotator(), actorSpawnParams); + newActor->GetRootComponent()->SetMobility(EComponentMobility::Movable); + newActor->AttachToActor(this, FAttachmentTransformRules::KeepRelativeTransform); + + if (staticMeshObjPtrRef->IsPending()) + { + staticMeshObjPtrRef->LoadSynchronous(); + } + newActor->GetStaticMeshComponent()->SetStaticMesh(staticMeshObjPtrRef->Get()); + + UOculusXRSceneAnchorComponent* sceneAnchorComponent = NewObject<UOculusXRSceneAnchorComponent>(newActor, sceneAnchorComponentInstanceClass); + sceneAnchorComponent->CreationMethod = EComponentCreationMethod::Instance; + newActor->AddOwnedComponent(sceneAnchorComponent); + + OculusXRAnchors::FOculusXRAnchors::SetAnchorComponentStatus(sceneAnchorComponent, EOculusXRSpaceComponentType::Locatable, true, 0.0f, FOculusXRAnchorSetComponentStatusDelegate()); + + sceneAnchorComponent->RegisterComponent(); + sceneAnchorComponent->InitializeComponent(); + sceneAnchorComponent->Activate(); + sceneAnchorComponent->SetHandle(Space); + sceneAnchorComponent->SemanticClassifications = SemanticClassifications; + sceneAnchorComponent->ForceParallelToFloor = foundProperties->ForceParallelToFloor; + sceneAnchorComponent->AddOffset = foundProperties->AddOffset; + + // Setup scale based on bounded size and the actual size of the mesh + UStaticMesh* staticMesh = newActor->GetStaticMeshComponent()->GetStaticMesh(); + FBoxSphereBounds staticMeshBounds; + staticMeshBounds.BoxExtent = FVector{1.f, 1.f, 1.f}; + if (staticMesh) + { + staticMeshBounds = staticMesh->GetBounds(); + } + const float worldToMeters = GetWorld()->GetWorldSettings()->WorldToMeters; + + newActor->SetActorScale3D(FVector( + (BoundedSize.X / (staticMeshBounds.BoxExtent.X * 2.f)) * worldToMeters, + (BoundedSize.Y / (staticMeshBounds.BoxExtent.Y * 2.f)) * worldToMeters, + (BoundedSize.Z / (staticMeshBounds.BoxExtent.Z * 2.f)) * worldToMeters) + ); + + return true; +} + +bool AOculusXRSceneActor::IsScenePopulated() +{ + if (!RootComponent) + return false; + return RootComponent->GetNumChildrenComponents() > 0; +} + +bool AOculusXRSceneActor::IsRoomLayoutValid() +{ + return bRoomLayoutIsValid; +} + +void AOculusXRSceneActor::PopulateScene() +{ + if (!RootComponent) + return; + + if (IsScenePopulated()) + { + UE_LOG(LogOculusXRScene, Display, TEXT("PopulateScene Scene is already populated. Clear it first.")); + return; + } + + const bool bResult = QuerySpatialAnchors(true); + if (bResult) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("PopulateScene Made a request to query spatial anchors")); + } + } + else + { + UE_LOG(LogOculusXRScene, Error, TEXT("PopulateScene Failed to query spatial anchors!")); + } +} + +void AOculusXRSceneActor::ClearScene() +{ + if (!RootComponent) + return; + + TArray<USceneComponent*> childrenComponents = RootComponent->GetAttachChildren(); + for (USceneComponent* SceneComponent : childrenComponents) + { + Cast<AActor>(SceneComponent->GetOuter())->Destroy(); + } + + bRoomLayoutIsValid = false; + bFoundCapturedScene = false; +} + +void AOculusXRSceneActor::SetVisibilityToAllSceneAnchors(const bool bIsVisible) +{ + if (!RootComponent) + return; + + TArray<USceneComponent*> childrenComponents = RootComponent->GetAttachChildren(); + for (USceneComponent* sceneComponent : childrenComponents) + { + sceneComponent->SetVisibility(bIsVisible); + } +} + +void AOculusXRSceneActor::SetVisibilityToSceneAnchorsBySemanticLabel(const FString SemanticLabel, const bool bIsVisible) +{ + if (!RootComponent) + return; + + TArray<USceneComponent*> childrenComponents = RootComponent->GetAttachChildren(); + for (USceneComponent* sceneComponent : childrenComponents) + { + UObject* outerObject = sceneComponent->GetOuter(); + if (!outerObject) + { + continue; + } + + AActor* outerActor = Cast<AActor>(outerObject); + if (!outerActor) + { + continue; + } + + UActorComponent* sceneAnchorComponent = outerActor->GetComponentByClass(UOculusXRSceneAnchorComponent::StaticClass()); + if (!sceneAnchorComponent) + { + continue; + } + + if (Cast<UOculusXRSceneAnchorComponent>(sceneAnchorComponent)->SemanticClassifications.Contains(SemanticLabel)) + { + sceneComponent->SetVisibility(bIsVisible); + } + } +} + +TArray<AActor*> AOculusXRSceneActor::GetActorsBySemanticLabel(const FString SemanticLabel) +{ + TArray<AActor*> actors; + + if (!RootComponent) + return actors; + + TArray<USceneComponent*> childrenComponents = RootComponent->GetAttachChildren(); + for (USceneComponent* sceneComponent : childrenComponents) + { + UObject* outerObject = sceneComponent->GetOuter(); + if (!outerObject) + { + continue; + } + + AActor* outerActor = Cast<AActor>(outerObject); + if (!outerActor) + { + continue; + } + + UActorComponent* sceneAnchorComponent = outerActor->GetComponentByClass(UOculusXRSceneAnchorComponent::StaticClass()); + if (!sceneAnchorComponent) + { + continue; + } + + if (Cast<UOculusXRSceneAnchorComponent>(sceneAnchorComponent)->SemanticClassifications.Contains(SemanticLabel)) + { + actors.Add(outerActor); + } + } + + return actors; +} + + +// DELEGATE HANDLERS +void AOculusXRSceneActor::AnchorQueryComplete_Handler(bool Success, const TArray<FOculusXRSpaceQueryResult>& Results) +{ + for (auto& Result : Results) + { + // Call the existing logic for each result + SpatialAnchorQueryResult_Handler(0, Result.Space, Result.UUID); + } + + // Call the complete handler at the end to check if we need to do room layout stuff + SpatialAnchorQueryComplete_Handler(0, Success); +} + +void AOculusXRSceneActor::SpatialAnchorQueryResult_Handler(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID Uuid) +{ + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler (requestId = %llu, space = %llu, uuid = %s"), RequestId.Value, Space.Value, *BytesToHex(Uuid.UUIDBytes, OCULUSXR_UUID_SIZE)); + } + + bool bResult = false; + bool bOutPending = false; + bool bIsRoomLayout = false; + + bResult = OculusXRAnchors::FOculusXRAnchorManager::GetSpaceComponentStatus(Space.Value, EOculusXRSpaceComponentType::RoomLayout, bIsRoomLayout, bOutPending); + + if (bResult && bIsRoomLayout) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler Found a room layout.")); + } + + // This is a room layout. We can now populate it with the room layout manager component + FOculusXRRoomLayout roomLayout; + const bool bGetRoomLayoutResult = RoomLayoutManagerComponent->GetRoomLayout(Space, roomLayout, MaxQueries); + if (bGetRoomLayoutResult) + { + // If we get here, then we know that captured scene was already created by the end-user in Capture Flow + bFoundCapturedScene = true; + + // We can now validate the room + bRoomLayoutIsValid = true; + + bRoomLayoutIsValid &= IsValidUuid(roomLayout.CeilingUuid); + bRoomLayoutIsValid &= IsValidUuid(roomLayout.FloorUuid); + bRoomLayoutIsValid &= roomLayout.WallsUuid.Num() > 3; + + for (int32 i = 0; i < roomLayout.WallsUuid.Num(); ++i) + { + bRoomLayoutIsValid &= IsValidUuid(roomLayout.WallsUuid[i]); + } + + if (bRoomLayoutIsValid || !bEnsureRoomIsValid) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler Room is valid = %d (# walls = %d)."), bRoomLayoutIsValid, roomLayout.WallsUuid.Num()); + } + + // We found a valid room, we can now query all ScenePlane/SceneVolume anchors + bResult = QuerySpatialAnchors(false); + if (bResult) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler Made a request to query spatial anchors")); + } + } + else + { + UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to query spatial anchors!")); + } + } + else + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler Room is invalid.")); + ClearScene(); + LaunchCaptureFlowIfNeeded(); + } + } + } + else + { + // Is it a ScenePlane anchor? + bool bIsScenePlane = false; + bResult = OculusXRAnchors::FOculusXRAnchorManager::GetSpaceComponentStatus(Space.Value, EOculusXRSpaceComponentType::ScenePlane, bIsScenePlane, bOutPending); + if (bResult && bIsScenePlane) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler Found a ScenePlane anchor.")); + } + + FVector scenePlanePos; + FVector scenePlaneSize; + bResult = OculusXRAnchors::FOculusXRAnchors::GetSpaceScenePlane(Space.Value, scenePlanePos, scenePlaneSize); + if (bResult) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler ScenePlane pos = [%.2f, %.2f, %.2f], size = [%.2f, %.2f, %.2f]."), + scenePlanePos.X, scenePlanePos.Y, scenePlanePos.Z, + scenePlaneSize.X, scenePlaneSize.Y, scenePlaneSize.Z); + } + } + else + { + UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to get bounds for ScenePlane space.")); + } + + TArray<FString> semanticClassifications; + bResult = OculusXRAnchors::FOculusXRAnchors::GetSpaceSemanticClassification(Space.Value, semanticClassifications); + if (bResult) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler Semantic Classifications:")); + for (FString& label : semanticClassifications) + { + UE_LOG(LogOculusXRScene, Display, TEXT("%s"), *label); + } + } + } + else + { + UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to get semantic classification space.")); + } + + if (!SpawnSceneAnchor(Space, scenePlaneSize, semanticClassifications, EOculusXRSpaceComponentType::ScenePlane)) + { + UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to spawn scene anchor.")); + } + } + else + { + // Is it a scenevolume anchor? + bool bIsSceneVolume = false; + bResult = OculusXRAnchors::FOculusXRAnchorManager::GetSpaceComponentStatus(Space.Value, EOculusXRSpaceComponentType::SceneVolume, bIsSceneVolume, bOutPending); + if (bResult && bIsSceneVolume) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler Found a SceneVolume anchor.")); + } + + FVector sceneVolumePos; + FVector sceneVolumeSize; + bResult = OculusXRAnchors::FOculusXRAnchors::GetSpaceSceneVolume(Space.Value, sceneVolumePos, sceneVolumeSize); + if (bResult) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler SceneVolume pos = [%.2f, %.2f, %.2f], size = [%.2f, %.2f, %.2f]."), + sceneVolumePos.X, sceneVolumePos.Y, sceneVolumePos.Z, + sceneVolumeSize.X, sceneVolumeSize.Y, sceneVolumeSize.Z); + } + } + else + { + UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to get bounds for SceneVolume space.")); + } + + TArray<FString> semanticClassifications; + bResult = OculusXRAnchors::FOculusXRAnchors::GetSpaceSemanticClassification(Space.Value, semanticClassifications); + if (bResult) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryResult_Handler Semantic Classifications:")); + for (FString& label : semanticClassifications) + { + UE_LOG(LogOculusXRScene, Display, TEXT("%s"), *label); + } + } + } + else + { + UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to get semantic classifications space.")); + } + + if (!SpawnSceneAnchor(Space, sceneVolumeSize, semanticClassifications, EOculusXRSpaceComponentType::SceneVolume)) + { + UE_LOG(LogOculusXRScene, Error, TEXT("SpatialAnchorQueryResult_Handler Failed to spawn scene anchor.")); + } + } + } + } +} + +void AOculusXRSceneActor::SpatialAnchorQueryComplete_Handler(FOculusXRUInt64 RequestId, bool bResult) +{ + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryComplete_Handler (requestId = %llu)"), RequestId.Value); + } + + if (!bFoundCapturedScene) // only try to launch capture flow if a captured scene was not found + { + if (!bResult) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SpatialAnchorQueryComplete_Handler No scene found.")); + } + LaunchCaptureFlowIfNeeded(); + } + } +} + +void AOculusXRSceneActor::SceneCaptureComplete_Handler(FOculusXRUInt64 RequestId, bool bResult) +{ + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("SceneCaptureComplete_Handler (requestId = %llu)"), RequestId); + } + + if (!bResult) + { + UE_LOG(LogOculusXRScene, Error, TEXT("Scene Capture Complete failed!")); + return; + } + + // Mark that we already launched Capture Flow and try to query spatial anchors again + bCaptureFlowWasLaunched = true; + + ClearScene(); + + const bool bQueryResult = QuerySpatialAnchors(true); + if (bResult) + { + if (bVerboseLog) + { + UE_LOG(LogOculusXRScene, Display, TEXT("Made a request to query spatial anchors")); + } + } + else + { + UE_LOG(LogOculusXRScene, Error, TEXT("Failed to query spatial anchors!")); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneAnchorComponent.cpp b/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneAnchorComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..06df1442cb697c69f4d40000068c0b8eb2e1790d --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneAnchorComponent.cpp @@ -0,0 +1,63 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#include "OculusXRSceneAnchorComponent.h" + +#include "Engine/StaticMeshActor.h" + + +UOculusXRSceneAnchorComponent::UOculusXRSceneAnchorComponent(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) +{ + bUpdateHeadSpaceTransform = false; +} + +void UOculusXRSceneAnchorComponent::TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) +{ + Super::TickComponent(DeltaTime, TickType, ThisTickFunction); + + AActor* Parent = GetOwner(); + + if (Parent == nullptr) + { + return; + } + + if (GetHandle().Value == 0) + { + return; + } + + if (ForceParallelToFloor) + { + FRotator rotation = Parent->GetActorRotation(); + FVector angles = rotation.Euler(); + bool isNeg = angles.Y < 0.f; + angles.Y = 90 * int32((abs(angles.Y) / 90.f) + 0.5f); + + if (isNeg) + { + angles.Y *= -1.f; + } + + rotation = rotation.MakeFromEuler(angles); + Parent->SetActorRotation(rotation, ETeleportType::ResetPhysics); + } + + if(!AddOffset.Equals(FVector::ZeroVector)) + { + const auto ParentActor = Cast<AStaticMeshActor>(Parent); + if(ParentActor != nullptr) + { + const auto SMComponent = ParentActor->GetStaticMeshComponent(); + if (SMComponent != nullptr) + { + SMComponent->AddLocalOffset(AddOffset, false, nullptr, ETeleportType::ResetPhysics); + } + } + } +} \ No newline at end of file diff --git a/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneModule.cpp b/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7de3ad9a7ad1f4a4bb690389e27a71573498e39e --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneModule.cpp @@ -0,0 +1,30 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#include "OculusXRSceneModule.h" + +#if OCULUS_SCENE_SUPPORTED_PLATFORMS +#include "OculusXRHMDModule.h" + +DEFINE_LOG_CATEGORY(LogOculusXRScene); + +#define LOCTEXT_NAMESPACE "OculusXRScene" + + +//------------------------------------------------------------------------------------------------- +// FOculusXRSceneModule +//------------------------------------------------------------------------------------------------- +void FOculusXRSceneModule::StartupModule() +{ + +} + +void FOculusXRSceneModule::ShutdownModule() +{ + +} + +#endif // OCULUS_SCENE_SUPPORTED_PLATFORMS + +IMPLEMENT_MODULE( FOculusXRSceneModule, OculusXRScene ) + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneModule.h b/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneModule.h new file mode 100644 index 0000000000000000000000000000000000000000..e9990fb0deaa0068220d13329e6583944d1a7fc9 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRScene/Private/OculusXRSceneModule.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "IOculusXRSceneModule.h" + +#define LOCTEXT_NAMESPACE "OculusXRScene" + + +//------------------------------------------------------------------------------------------------- +// FOculusXRSceneModule +//------------------------------------------------------------------------------------------------- + +#if OCULUS_SCENE_SUPPORTED_PLATFORMS + +DECLARE_LOG_CATEGORY_EXTERN(LogOculusXRScene, Log, All); + +class FOculusXRSceneModule : public IOculusXRSceneModule +{ +public: + virtual ~FOculusXRSceneModule() = default; + + // IModuleInterface interface + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +private: +}; + +#else // OCULUS_SCENE_SUPPORTED_PLATFORMS + +class FOculusXRSceneModule : public FDefaultModuleImpl +{ + +}; + +#endif // OCULUS_SCENE_SUPPORTED_PLATFORMS + + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/OculusXR/Source/OculusXRScene/Public/IOculusXRSceneModule.h b/Plugins/OculusXR/Source/OculusXRScene/Public/IOculusXRSceneModule.h new file mode 100644 index 0000000000000000000000000000000000000000..6848251cef52df4e3a937164dde376cf09d93bb8 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRScene/Public/IOculusXRSceneModule.h @@ -0,0 +1,39 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +#pragma once +#include "Modules/ModuleManager.h" + +#define OCULUS_SCENE_SUPPORTED_PLATFORMS (PLATFORM_WINDOWS && WINVER > 0x0502) || (PLATFORM_ANDROID_ARM || PLATFORM_ANDROID_ARM64) + + +/** + * The public interface to this module. In most cases, this interface is only public to sibling modules + * within this plugin. + */ +class IOculusXRSceneModule : 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 IOculusXRSceneModule& Get() + { + return FModuleManager::LoadModuleChecked< IOculusXRSceneModule >( "OculusXRScene" ); + } + + /** + * 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( "OculusXRScene" ); + } +}; + diff --git a/Plugins/OculusXR/Source/OculusXRScene/Public/OculusXRSceneActor.h b/Plugins/OculusXR/Source/OculusXRScene/Public/OculusXRSceneActor.h new file mode 100644 index 0000000000000000000000000000000000000000..45d81f2bbf52fec8010312315230938d215515f2 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRScene/Public/OculusXRSceneActor.h @@ -0,0 +1,158 @@ +// Copyright Epic Games, Inc. All Rights Reserved. +#pragma once + +#include "GameFramework/Actor.h" +#include "OculusXRRoomLayoutManagerComponent.h" +#include "OculusXRAnchorComponent.h" +#include "OculusXRSceneAnchorComponent.h" +#include "OculusXRSceneActor.generated.h" + + +/** EOculusXRLaunchCaptureFlowWhenMissingScene +* Used to dictate whether the actor should launch the Capture Flow application when a scene is not detected on the device. +* The Actor will check if a scene capture is either non-existent or invalid (ie. missing walls/ceiling/floor) before checking if Capture Flow +* should be launched. +* +* NEVER: will never launch Flow Capture. +* ONCE: will only launch it once. If the actor still doesn't detect that a scene was captured, it will not launch Capture Flow again. +* ALWAYS: will always re-launch Flow Capture if a scene was not detected on the device. +*/ +UENUM(BlueprintType) +enum EOculusXRLaunchCaptureFlowWhenMissingScene +{ + NEVER UMETA(DisplayName = "Never"), + ONCE UMETA(DisplayName = "Once"), + ALWAYS UMETA(DisplayName = "Always") +}; + +/** FOculusXRSpawnedSceneAnchorProperties +* Properties/Components that a spawned scene anchor will use. +*/ +USTRUCT(BlueprintType) +struct FOculusXRSpawnedSceneAnchorProperties +{ + GENERATED_BODY() + + UPROPERTY(EditAnywhere, Category = "Spawned Scene Anchor Properties") + TSoftClassPtr<UOculusXRSceneAnchorComponent> ActorComponent = TSoftClassPtr<UOculusXRSceneAnchorComponent>(FSoftClassPath(UOculusXRSceneAnchorComponent::StaticClass())); + + UPROPERTY(EditAnywhere, Category = "Spawned Scene Anchor Properties") + TSoftObjectPtr<UStaticMesh> StaticMesh; + + UPROPERTY(EditAnywhere, Category = "Spawned Scene Anchor Properties") + bool ForceParallelToFloor = false; + + UPROPERTY(EditAnywhere, Category = "Spawned Scene Anchor Properties") + FVector AddOffset = FVector::ZeroVector; +}; + + +/** +* AOculusXRSceneActor +* The purpose of this actor is to be able to spawn "scene anchor" actors. +* +* Each actor type (based on their semantic label) can be configured to be spawned with a specific mesh and actor component. +* +* Overall, it provides a simple interface to be able to quickly get a captured scene from Capture Flow populated at runtime. +* It also provides a basic and flexible template to making use of the OculusAnchorSDK and UOculusXRRoomLayoutManagerComponent +* to drive the actor's logic. This removes the need for the developer to implement a system from scratch that makes use of +* the native methods and components. +* +* TLDR: +* - This actor populates a captured scene (created in Capture Flow) by spawning child actors with predefined actor and mesh components. +* - Can be used as is, or can be derived or modified as needed depending on the application's needs. +*/ +UCLASS(ClassGroup = OculusXRScene) +class OCULUSXRSCENE_API AOculusXRSceneActor : public AActor +{ + GENERATED_BODY() + +public: + UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor") + void LaunchCaptureFlow(); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor") + bool IsScenePopulated(); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor") + bool IsRoomLayoutValid(); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor") + void PopulateScene(); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor") + void ClearScene(); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor") + void SetVisibilityToAllSceneAnchors(const bool bIsVisible); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor") + void SetVisibilityToSceneAnchorsBySemanticLabel(const FString SemanticLabel, const bool bIsVisible); + + UFUNCTION(BlueprintCallable, Category = "OculusXR|Scene Actor") + TArray<AActor*> GetActorsBySemanticLabel(const FString SemanticLabel); + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor") + bool bEnsureRoomIsValid = true; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor") + TEnumAsByte<EOculusXRLaunchCaptureFlowWhenMissingScene> LauchCaptureFlowWhenMissingScene = EOculusXRLaunchCaptureFlowWhenMissingScene::ALWAYS; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor", meta = (UIMin = 1, ClampMin = 1, UIMax = 1024, ClampMax = 1024)) + int32 MaxQueries = 64; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor") + bool bVerboseLog = false; + + UPROPERTY(EditAnywhere, BlueprintReadWrite, Category = "OculusXR|Scene Actor") + bool bPopulateSceneOnBeginPlay = true; + + UPROPERTY(EditAnywhere, Category = "OculusXR|Scene Actor") + TMap<FString, FOculusXRSpawnedSceneAnchorProperties> ScenePlaneSpawnedSceneAnchorProperties; + + UPROPERTY(EditAnywhere, Category = "OculusXR|Scene Actor") + TMap<FString, FOculusXRSpawnedSceneAnchorProperties> SceneVolumeSpawnedSceneAnchorProperties; + +public: + AOculusXRSceneActor(const FObjectInitializer& ObjectInitializer); + + virtual void BeginPlay() override; + virtual void EndPlay(EEndPlayReason::Type Reason) override; + virtual void Tick(float DeltaTime) override; + + +private: + // Event delegate handlers + void AnchorQueryComplete_Handler(bool Success, const TArray<FOculusXRSpaceQueryResult>& Results); + + void SpatialAnchorQueryResult_Handler(FOculusXRUInt64 RequestId, FOculusXRUInt64 Space, FOculusXRUUID Uuid); + void SpatialAnchorQueryComplete_Handler(FOculusXRUInt64 RequestId, bool bResult); + void SceneCaptureComplete_Handler(FOculusXRUInt64 RequestId, bool bResult); + + // Launches Capture Flow if (based on LauchCaptureFlowWhenMissingScene member value) + void LaunchCaptureFlowIfNeeded(); + + // Resets states of the Actor + void ResetStates(); + + // Handles logic for making a spatial anchors request + bool QuerySpatialAnchors(const bool bRoomLayoutOnly); + + // Validates UUID + bool IsValidUuid(const FOculusXRUUID& Uuid); + + // Spawns a scene anchor + bool SpawnSceneAnchor(const FOculusXRUInt64& Space, const FVector& BoundedSize, const TArray<FString>& SemanticClassifications, const EOculusXRSpaceComponentType AnchorComponentType); + + // Components for room layout and spatial anchors functionalities + UOculusXRRoomLayoutManagerComponent* RoomLayoutManagerComponent = nullptr; + + // Whether Capture Flow was already launched once + bool bCaptureFlowWasLaunched; + + // Whether last room layout was valid + bool bRoomLayoutIsValid; + + // Whether we found a captured scene + bool bFoundCapturedScene; +}; diff --git a/Plugins/OculusXR/Source/OculusXRScene/Public/OculusXRSceneAnchorComponent.h b/Plugins/OculusXR/Source/OculusXRScene/Public/OculusXRSceneAnchorComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..c53136bc3fe71bc3a71f43f4ff690efd6d8819f1 --- /dev/null +++ b/Plugins/OculusXR/Source/OculusXRScene/Public/OculusXRSceneAnchorComponent.h @@ -0,0 +1,33 @@ +/* +Copyright (c) Meta Platforms, Inc. and affiliates. +All rights reserved. + +This source code is licensed under the license found in the +LICENSE file in the root directory of this source tree. +*/ + +#pragma once + +#include "CoreMinimal.h" +#include "OculusXRAnchorComponent.h" +#include "OculusXRSceneAnchorComponent.generated.h" + +UCLASS(meta = (DisplayName = "OculusXR Scene Anchor Component", BlueprintSpawnableComponent)) +class UOculusXRSceneAnchorComponent : public UOculusXRAnchorComponent +{ + GENERATED_BODY() + + virtual void TickComponent(float DeltaTime, enum ELevelTick TickType, FActorComponentTickFunction* ThisTickFunction) override; + +public: + UOculusXRSceneAnchorComponent(const FObjectInitializer& ObjectInitializer); + + UPROPERTY(Transient, BlueprintReadOnly, Category = "OculusXR|Scene Anchor Component") + TArray<FString> SemanticClassifications; + + UPROPERTY(EditAnywhere, Category = "OculusXR|Scene Anchor Component") + bool ForceParallelToFloor = false; + + UPROPERTY(EditAnywhere, Category = "OculusXR|Scene Anchor Component") + FVector AddOffset = FVector::ZeroVector; +}; diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/ExtLibs/SystemUtils.jar b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/ExtLibs/SystemUtils.jar new file mode 100644 index 0000000000000000000000000000000000000000..f0085057ebfd4943f43e199df675ecbfee4ae5f8 Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/ExtLibs/SystemUtils.jar differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/ExtLibs/vrplatlib.jar b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/ExtLibs/vrplatlib.jar new file mode 100644 index 0000000000000000000000000000000000000000..975d736cda10c3fe67f50610d3f6484fc8cf6e55 Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/ExtLibs/vrplatlib.jar differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin.h new file mode 100644 index 0000000000000000000000000000000000000000..91e6f2d4109f18ef2bdfbea7d2f9f1e1d5ec16ab --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin.h @@ -0,0 +1,1033 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVR_Plugin_h +#define OVR_Plugin_h + +#include "OVR_Plugin_Types.h" + +// OVR_Plugin.h: Minimal high-level plugin proxy to LibOVR. Use instead of OVR_CAPI.h. +// All functions must be called from the same thread as your graphics device, except as noted. + +#ifdef __cplusplus +extern "C" { +#endif + +// The following macros are only defined when building in UE4 +#if ( \ + defined(UE_BUILD_DEBUG) || defined(UE_BUILD_DEVELOPMENT) || defined(UE_BUILD_TEST) || \ + defined(UE_BUILD_SHIPPING)) && \ + defined(PLATFORM_ANDROID) +// OVRPlugin does not support Android system callbacks in UE4. +// Please use Optional Mobile Features Blueprint Library Plugin or include FAndroidMisc.h in your project +#define OVRPLUGIN_JNI_LIB_EXCLUDED +#endif + +/// Initializes the Oculus display driver before graphics initialization, if applicable. +OVRP_EXPORT ovrpResult +ovrp_PreInitialize5(void* activity, ovrpRenderAPIType apiType, ovrpPreinitializeFlags preinitializeFlags); + +/// Gets the current initialization state of the Oculus runtime, VR tracking, and graphics +/// resources. +OVRP_EXPORT ovrpBool ovrp_GetInitialized(); + +/// Sets up the Oculus runtime, VR tracking, and graphics resources. +/// You must call this before any other function except ovrp_PreInitialize() or +/// ovrp_GetInitialized(). +/// @note In case of D3D12, d3dDevice is an ID3D12CommandQueue object +OVRP_EXPORT ovrpResult ovrp_Initialize7( + ovrpRenderAPIType apiType, + ovrpLogCallback logCallback, + void* activity, + void* vkInstance, + void* vkPhysicalDevice, + void* vkDevice, + void* vkQueue, + void* vkGetInstanceProcAddr, // PFN_vkGetInstanceProcAddr + unsigned int vkQueueFamilyIndex, + void* d3dDevice, + int initializeFlags, + OVRP_CONSTREF(ovrpVersion) version); + +/// Tears down the Oculus runtime, VR tracking, and graphics resources. +OVRP_EXPORT ovrpResult ovrp_Shutdown2(); + +/// Set a function pointer which captures the OVRPlugin log lines. +OVRP_EXPORT ovrpResult ovrp_SetLogCallback2(ovrpLogCallback2 logFunc); + +/// Gets the version of OVRPlugin currently in use. Format: "major.minor.release" +OVRP_EXPORT ovrpResult ovrp_GetVersion2(char const** version); + +/// Gets the version of the underlying VR SDK currently in use. +OVRP_EXPORT ovrpResult ovrp_GetNativeSDKVersion2(char const** nativeSDKVersion); + +/// Returns a pointer that can be used to access the underlying VR SDK +/// (e.g. ovrSession in CAPI, ovrMobile* in VRAPI, XrSession* in OpenXR). +OVRP_EXPORT ovrpResult ovrp_GetNativeSDKPointer2(void** nativeSDKPointer); + +/// Retreive the current XR API being used by OVRPlugin +OVRP_EXPORT ovrpResult ovrp_GetNativeXrApiType(ovrpXrApi* xrApi); + +/// Retrive XrInstance / XrSession when OpenXR is being used +OVRP_EXPORT ovrpResult ovrp_GetNativeOpenXRHandles(ovrpUInt64* xrInstance, ovrpUInt64* xrSession); + +/// Retrieves the expected Display Adapter ID associated with the Oculus HMD. +/// On Windows systems, this will return a DX11 LUID, otherwise nullptr. +/// @note ovrp_PreInitialize must be called and return a successful result before calling this +/// function. +OVRP_EXPORT ovrpResult ovrp_GetDisplayAdapterId2(void const** displayAdapterId); + +/// Retrieves the expected audio device ID associated with the Oculus HMD's headphones. +/// On Windows systems, this will return the GUID* for the IMMDevice of an audio endpoint. +/// @note ovrp_PreInitialize must be called and return a successful result before calling this +/// function. +OVRP_EXPORT ovrpResult ovrp_GetAudioOutId2(void const** audioOutId); + +/// Retrieves the expected audio device ID associated with the Oculus HMD's headphones. +/// On Windows systems, this will return a LPCWSTR containing the device identifier. +/// @note ovrp_PreInitialize must be called and return a successful result before calling this +/// function. +OVRP_EXPORT ovrpResult ovrp_GetAudioOutDeviceId2(void const** audioOutDeviceId); + +/// Retrieves the expected audio device ID associated with the Oculus HMD's microphone. +/// On Windows systems, this will return the GUID* for the IMMDevice of an audio endpoint. +/// @note ovrp_PreInitialize must be called and return a successful result before calling this +/// function. +OVRP_EXPORT ovrpResult ovrp_GetAudioInId2(void const** audioInId); + +/// Retrieves the expected audio device ID associated with the Oculus HMD's microphone. +/// On Windows systems, this will return a LPCWSTR containing the device identifier. +/// @note ovrp_PreInitialize must be called and return a successful result before calling this +/// function. +OVRP_EXPORT ovrpResult ovrp_GetAudioInDeviceId2(void const** audioInDeviceId); + +/// Returns an array of pointers to extension names which need to be enabled for the instance +/// in order for the VR runtime to support Vulkan-based applications. +/// @note ovrp_PreInitialize must be called and return a successful result before calling this +/// function. +OVRP_EXPORT ovrpResult ovrp_GetInstanceExtensionsVk(char const** instanceExtensions, int* instanceExtensionCount); + +/// Returns an array of pointers to extension names which need to be enabled for the device +/// in order for the VR runtime to support Vulkan-based applications. +/// @note ovrp_PreInitialize must be called and return a successful result before calling this +/// function. +OVRP_EXPORT ovrpResult ovrp_GetDeviceExtensionsVk(char const** deviceExtensions, int* deviceExtensionCount); + +/// Creates a dedicated window for rendering 3D content to the VR display. +OVRP_EXPORT ovrpResult ovrp_SetupDistortionWindow3(int flags); + +/// Destroys the dedicated VR window. +OVRP_EXPORT ovrpResult ovrp_DestroyDistortionWindow2(); + +// Returns handedness as specified in the mobile device +OVRP_EXPORT ovrpResult ovrp_GetDominantHand(ovrpHandedness* dominantHand); + +/// Used by System Activities application for setting the Remote Handedness. +OVRP_EXPORT ovrpResult ovrp_SetRemoteHandedness(ovrpHandedness handedness); + +// Sets color scale parameters; can be used for effects like fade-to-black. Final pixel color will be multiplied by +// colorScale and added to offset. If applyToAllLayers is false, this applies only for the eyefov layer. If it's true, +// it's for every layer submitted. +OVRP_EXPORT ovrpResult ovrp_SetColorScaleAndOffset( + const ovrpVector4f colorScale, + const ovrpVector4f colorOffset, + const ovrpBool applyToAllLayers); + +/// Creates a layer. +/// The desc remains constant for the lifetime of the layer. +/// @note In case of D3D12, device is an ID3D12CommandQueue object +OVRP_EXPORT ovrpResult ovrp_SetupLayer(void* device, OVRP_CONSTREF(ovrpLayerDesc) desc, int* layerId); + +/// Create depth swap chain for a layer +OVRP_EXPORT ovrpResult ovrp_SetupLayerDepth(void* device, ovrpTextureFormat depthFormat, int layerId); + +/// Get Eye Fov layer index if created +/// Otherwise return fail +OVRP_EXPORT ovrpResult ovrp_GetEyeFovLayerId(int* layerId); + +/// Set blending mode of Eye Fov layer to use premultiplied alpha or not +OVRP_EXPORT ovrpResult ovrp_SetEyeFovPremultipliedAlphaMode(const ovrpBool enabled); + +/// Get premultiplied alpha mode of the Eye Fov layer +OVRP_EXPORT ovrpResult ovrp_GetEyeFovPremultipliedAlphaMode(ovrpBool* enabled); + +/// Gets the number of texture stages in the layer. +/// Layers have multiple stages, unless the ovrpLayer_Static flag was specified. +OVRP_EXPORT ovrpResult ovrp_GetLayerTextureStageCount(int layerId, int* layerTextureStageCount); + +/// Gets the texture handle for a specific layer stage and eye. +OVRP_EXPORT ovrpResult ovrp_GetLayerTexture2( + int layerId, + int stage, + ovrpEye eyeId, + ovrpTextureHandle* textureHandle, + ovrpTextureHandle* depthTextureHandle); + +/// Gets the texture handle for a specific layer stage and eye. +OVRP_EXPORT ovrpResult ovrp_GetLayerTextureFoveation( + int layerId, + int stage, + ovrpEye eyeId, + ovrpTextureHandle* foveationTextureHandle, + ovrpSizei* foveationResultSize); + +/// Gets the space warp texture handles for a specific layer stage and eye. +OVRP_EXPORT ovrpResult ovrp_GetLayerTextureSpaceWarp( + int layerId, + int stage, + ovrpEye eyeId, + ovrpTextureHandle* motionVectorTextureHandle, + ovrpSizei* motionVectorResultSize, + ovrpTextureHandle* depthTextureHandle, + ovrpSizei* depthResultSize); + +/// Gets the texture handle for a specific layer stage and eye. +OVRP_EXPORT ovrpResult ovrp_GetLayerAndroidSurfaceObject(int layerId, void** surfaceObject); + +/// Return the vertices and indices for the eye occlusion mesh. +OVRP_EXPORT ovrpResult ovrp_GetLayerOcclusionMesh( + int layerId, + ovrpEye eyeId, + ovrpVector2f const** vertices, + int* vertexCount, + int const** indices, + int* indexCount); + +/// Destroys a layer +OVRP_EXPORT ovrpResult ovrp_DestroyLayer(int layerId); + +/// Calculates layer description +OVRP_EXPORT ovrpResult ovrp_CalculateLayerDesc( + ovrpShape shape, + ovrpLayout layout, + OVRP_CONSTREF(ovrpSizei) textureSize, + int mipLevels, + int sampleCount, + ovrpTextureFormat format, + int layerFlags, + ovrpLayerDescUnion* layerDesc); + +/// Calculates eye layer description +OVRP_EXPORT ovrpResult ovrp_CalculateEyeLayerDesc2( + ovrpLayout layout, + float textureScale, + int mipLevels, + int sampleCount, + ovrpTextureFormat format, + ovrpTextureFormat depthFormat, + int layerFlags, + ovrpLayerDesc_EyeFov* layerDesc); + +/// Calculates eye layer description +OVRP_EXPORT ovrpResult ovrp_CalculateEyeLayerDesc3( + ovrpLayout layout, + float textureScale, + int mipLevels, + int sampleCount, + ovrpTextureFormat format, + ovrpTextureFormat depthFormat, + ovrpTextureFormat motionVectorFormat, + ovrpTextureFormat motionVectorDepthFormat, + float motionVectorTextureScale, + int layerFlags, + ovrpLayerDesc_EyeFov* layerDesc); + +/// Calculates the recommended viewport rect for the specified eye +OVRP_EXPORT ovrpResult ovrp_CalculateEyeViewportRect( + OVRP_CONSTREF(ovrpLayerDesc_EyeFov) layerDesc, + ovrpEye eyeId, + float viewportScale, + ovrpRecti* viewportRect); + +/// Calculates the area of the viewport unobstructed by the occlusion mesh +OVRP_EXPORT ovrpResult ovrp_CalculateEyePreviewRect( + OVRP_CONSTREF(ovrpLayerDesc_EyeFov) layerDesc, + ovrpEye eyeId, + OVRP_CONSTREF(ovrpRecti) viewportRect, + ovrpRecti* previewRect); + +/// Allocates mirror texture +/// If you called ovrp_Initialize with ovrpRenderAPI_D3D11, pass device argument +/// here to have the texture allocated by that device. +/// If you called ovrp_Initialize with ovrpRenderAPI_OpenGL, you can pass +/// a texture ID allocated by glGenTextures in the device argument and the texture will be +/// associated with that ID. +/// @note In case of D3D12, device is an ID3D12CommandQueue object +OVRP_EXPORT ovrpResult ovrp_SetupMirrorTexture2( + void* device, + int height, + int width, + ovrpTextureFormat format, + ovrpTextureHandle* textureHandle); + +/// Destroys mirror texture. +OVRP_EXPORT ovrpResult ovrp_DestroyMirrorTexture2(); + +/// Returns the recommended amount to scale GPU work in order to maintain framerate. +/// Can be used to adjust viewportScale and textureScale +OVRP_EXPORT ovrpResult ovrp_GetAdaptiveGpuPerformanceScale2(float* adapiveGpuPerformanceScale); + +/// Returns the time from CPU start to GPU end, a meaningful performance metric under OVR +OVRP_EXPORT ovrpResult ovrp_GetAppCpuStartToGpuEndTime2(float* appCpuStartToGpuEndTime); + +/// Return how many display pixels will fit in tan(angle) = 1 +OVRP_EXPORT ovrpResult ovrp_GetEyePixelsPerTanAngleAtCenter2(int eyeIndex, ovrpVector2f* pixelsPerTanAngleAtCenter); + +/// Return the offset HMD to the eye, in meters +OVRP_EXPORT ovrpResult ovrp_GetHmdToEyeOffset2(int eyeIndex, ovrpVector3f* hmdToEyeOffset); + +/// Ensures VR rendering is configured and updates tracking to reflect the latest reported poses. +/// You must call ovrp_Update before calling ovrp_GetNode* for a new frame. +/// Call with ovrpStep_Render from end of frame on Game thread, to hand off state to Render thread +/// Call with ovrpStep_Physics from any thread, using predictionSeconds specify offset from start of +/// frame. +OVRP_EXPORT ovrpResult ovrp_Update3(ovrpStep step, int frameIndex, double predictionSeconds); + +/// Marks the beginning of a frame. Call this before issuing any graphics commands in a given frame. +OVRP_EXPORT ovrpResult ovrp_WaitToBeginFrame(int frameIndex); + +/// Marks the beginning of a frame. Call this before issuing any graphics commands in a given frame. +OVRP_EXPORT ovrpResult ovrp_BeginFrame4(int frameIndex, void* commandQueue); + +/// Late update of foveation parameters, both GL and Vulkan +OVRP_EXPORT ovrpResult ovrp_UpdateFoveation(int frameIndex); + +/// Marks the end of a frame and performs TimeWarp. Call this before Present or SwapBuffers to +/// update the VR window. +OVRP_EXPORT ovrpResult +ovrp_EndFrame4(int frameIndex, ovrpLayerSubmit const* const* layerSubmitPtrs, int layerSubmitCount, void* commandQueue); + +/// If true, the HMD supports orientation tracking. +OVRP_EXPORT ovrpResult ovrp_GetTrackingOrientationSupported2(ovrpBool* trackingOrientationSupported); + +/// If true, head tracking affects the rotation reported by ovrp_GetEyePose. +OVRP_EXPORT ovrpResult ovrp_GetTrackingOrientationEnabled2(ovrpBool* trackingOrientationEnabled); + +/// If true, head tracking affects the rotation reported by ovrp_GetEyePose. +OVRP_EXPORT ovrpResult ovrp_SetTrackingOrientationEnabled2(ovrpBool trackingOrientationEnabled); + +/// If true, the HMD supports position tracking +OVRP_EXPORT ovrpResult ovrp_GetTrackingPositionSupported2(ovrpBool* trackingPositionSupported); + +/// If true, head tracking affects the position reported by ovrp_GetEyePose. +OVRP_EXPORT ovrpResult ovrp_GetTrackingPositionEnabled2(ovrpBool* trackingPositionEnabled); + +/// If true, head tracking affects the position reported by ovrp_GetEyePose. +OVRP_EXPORT ovrpResult ovrp_SetTrackingPositionEnabled2(ovrpBool trackingPositionEnabled); + +/// If true, the inter-pupillary distance affects the position reported by ovrp_GetEyePose. +OVRP_EXPORT ovrpResult ovrp_GetTrackingIPDEnabled2(ovrpBool* trackingIPDEnabled); + +/// If true, the inter-pupillary distance affects the position reported by ovrp_GetEyePose. +OVRP_EXPORT ovrpResult ovrp_SetTrackingIPDEnabled2(ovrpBool trackingIPDEnabled); + +/// Gets the calibrated origin pose. +OVRP_EXPORT ovrpResult ovrp_GetTrackingCalibratedOrigin2(ovrpPosef* trackingCalibratedOrigin); + +/// Oculus Internal. Sets the system-wide calibrated origin for the currently active tracking origin +/// type. +OVRP_EXPORT ovrpResult ovrp_SetTrackingCalibratedOrigin2(); + +/// Gets the currently active tracking origin type. +OVRP_EXPORT ovrpResult ovrp_GetTrackingOriginType2(ovrpTrackingOrigin* trackingOriginType); + +/// Sets the currently active tracking origin type. +OVRP_EXPORT ovrpResult ovrp_SetTrackingOriginType2(ovrpTrackingOrigin trackingOriginType); + +/// Changes the frame of reference used by tracking. +/// See the ovrpRecenterFlag enum for details about available flags. +OVRP_EXPORT ovrpResult ovrp_RecenterTrackingOrigin2(unsigned int flags); + +/// If true, the node is considered present and available. +OVRP_EXPORT ovrpResult ovrp_GetNodePresent2(ovrpNode nodeId, ovrpBool* nodePresent); + +/// If true, the node's orientation is tracked. +OVRP_EXPORT ovrpResult ovrp_GetNodeOrientationTracked2(ovrpNode nodeId, ovrpBool* nodeOrientationTracked); + +/// If true, the node's orientation is valid. +OVRP_EXPORT ovrpResult ovrp_GetNodeOrientationValid(ovrpNode nodeId, ovrpBool* nodeOrientationValid); + +/// If true, the node's position is tracked. +OVRP_EXPORT ovrpResult ovrp_GetNodePositionTracked2(ovrpNode nodeId, ovrpBool* nodePositionTracked); + +/// If true, the node's position is valid. +OVRP_EXPORT ovrpResult ovrp_GetNodePositionValid(ovrpNode nodeId, ovrpBool* nodePositionValid); + +/// Force a node position to be tracked or not. Return false if the node's position tracking cannot +/// be changed. +OVRP_EXPORT ovrpResult ovrp_SetNodePositionTracked2(ovrpNode nodeId, ovrpBool nodePositionTracked); + +/// Gets the current pose, acceleration, and velocity of the given node on the given update cadence. +OVRP_EXPORT ovrpResult +ovrp_GetNodePoseState3(ovrpStep step, int frameIndex, ovrpNode nodeId, ovrpPoseStatef* nodePoseState); + +/// Gets the current pose, acceleration, and velocity of the given node on the given update cadence, without applying +/// any modifier (e.g. HeadPoseModifier) +OVRP_EXPORT ovrpResult +ovrp_GetNodePoseStateRaw(ovrpStep step, int frameIndex, ovrpNode nodeId, ovrpPoseStatef* nodePoseState); + +/// Gets the current frustum for the given node, if available. +OVRP_EXPORT ovrpResult ovrp_GetNodeFrustum2(ovrpNode nodeId, ovrpFrustum2f* nodeFrustum); + + + + + + + + + + + +/// Set relative rotation/translation to the eye pose +OVRP_EXPORT ovrpResult +ovrp_SetHeadPoseModifier(const ovrpQuatf* relativeRotation, const ovrpVector3f* relativeTranslation); + +/// Get current relative rotation/translation to the eye pose +OVRP_EXPORT ovrpResult ovrp_GetHeadPoseModifier(ovrpQuatf* relativeRotation, ovrpVector3f* relativeTranslation); + +/// Gets the controller state for the given controllers. +OVRP_EXPORT ovrpResult ovrp_GetControllerState4(ovrpController controllerMask, ovrpControllerState4* controllerState); + +/// Gets the currently active controller type. +OVRP_EXPORT ovrpResult ovrp_GetActiveController2(ovrpController* activeController); + +/// Gets the currently connected controller types as a bitmask. +OVRP_EXPORT ovrpResult ovrp_GetConnectedControllers2(ovrpController* connectedControllers); + +/// Sets the vibration state for the given controllers. +OVRP_EXPORT ovrpResult ovrp_SetControllerVibration2(ovrpController controllerMask, float frequency, float amplitude); + +/// Gets the controller state for the given controllers. +OVRP_EXPORT ovrpResult ovrp_GetControllerState5(ovrpController controllerMask, ovrpControllerState5* controllerState); + +/// Sets the vibration state for the specified haptics locations on the given controllers. +OVRP_EXPORT ovrpResult ovrp_SetControllerLocalizedVibration( + ovrpController controllerMask, + ovrpHapticsLocation hapticsLocationMask, + float frequency, + float amplitude); + +OVRP_EXPORT ovrpResult ovrp_SetControllerHapticsAmplitudeEnvelope( + ovrpController controllerMask, + ovrpHapticsAmplitudeEnvelopeVibration hapticsVibration); + +OVRP_EXPORT ovrpResult +ovrp_SetControllerHapticsPcm(ovrpController controllerMask, ovrpHapticsPcmVibration hapticsVibration); + + + + + + +/// Gets the current haptics desc for the given controllers. +OVRP_EXPORT ovrpResult +ovrp_GetControllerHapticsDesc2(ovrpController controllerMask, ovrpHapticsDesc* controllerHapticsDesc); + +/// Gets the current haptics state for the given controllers. +OVRP_EXPORT ovrpResult +ovrp_GetControllerHapticsState2(ovrpController controllerMask, ovrpHapticsState* controllerHapticsState); + +/// Gets the preferred sample rate (in Hz) for the given controller. +OVRP_EXPORT ovrpResult ovrp_GetControllerSampleRateHz(ovrpController controller, float* sampleRateHz); + +/// Sets the haptics buffer state for the given controllers. +OVRP_EXPORT ovrpResult ovrp_SetControllerHaptics2(ovrpController controllerMask, ovrpHapticsBuffer hapticsBuffer); + +/// Sets the suggested CPU performance level +OVRP_EXPORT ovrpResult ovrp_SetSuggestedCpuPerformanceLevel(ovrpProcessorPerformanceLevel perfLevel); + +/// Gets the suggested CPU performance level +OVRP_EXPORT ovrpResult ovrp_GetSuggestedCpuPerformanceLevel(ovrpProcessorPerformanceLevel* perfLevel); + +/// Sets the suggested CPU performance level +OVRP_EXPORT ovrpResult ovrp_SetSuggestedGpuPerformanceLevel(ovrpProcessorPerformanceLevel perfLevel); + +/// Gets the suggested CPU performance level +OVRP_EXPORT ovrpResult ovrp_GetSuggestedGpuPerformanceLevel(ovrpProcessorPerformanceLevel* perfLevel); + +/// Gets the current CPU performance level, integer in the range 0 - 3 (deprecated). +OVRP_EXPORT ovrpResult ovrp_GetSystemCpuLevel2(int* systemCpuLevel); + +/// Sets the current CPU performance level, integer in the range 0 - 3 (deprecated). +OVRP_EXPORT ovrpResult ovrp_SetSystemCpuLevel2(int systemCpuLevel); + +/// Returns true if the application should run at the maximum possible CPU level. +OVRP_EXPORT ovrpResult ovrp_GetAppCPUPriority2(ovrpBool* appCPUPriority); + +/// Determines whether the application should run at the maximum possible CPU level. +OVRP_EXPORT ovrpResult ovrp_SetAppCPUPriority2(ovrpBool appCPUPriority); + +/// Gets the current GPU performance level, integer in the range 0 - 3 (deprecated). +OVRP_EXPORT ovrpResult ovrp_GetSystemGpuLevel2(int* systemGpuLevel); + +/// Sets the current GPU performance level, integer in the range 0 - 3 (deprecated). +OVRP_EXPORT ovrpResult ovrp_SetSystemGpuLevel2(int systemGpuLevel); + +/// If true, the system is running in a reduced performance mode to save power. +OVRP_EXPORT ovrpResult ovrp_GetSystemPowerSavingMode2(ovrpBool* systemPowerSavingMode); + +/// Gets the current refresh rate of the HMD. +OVRP_EXPORT ovrpResult ovrp_GetSystemDisplayFrequency2(float* systemDisplayFrequency); + +/// Gets the available refresh rates of the HMD. +OVRP_EXPORT ovrpResult +ovrp_GetSystemDisplayAvailableFrequencies(float* systemDisplayAvailableFrequencies, int* arraySize); + +/// Sets the refresh rate for the HMD +OVRP_EXPORT ovrpResult ovrp_SetSystemDisplayFrequency(float requestedFrequency); + +/// Gets the minimum number of vsyncs to wait after each frame. +OVRP_EXPORT ovrpResult ovrp_GetSystemVSyncCount2(int* systemVSyncCount); + +/// Sets the minimum number of vsyncs to wait after each frame. +OVRP_EXPORT ovrpResult ovrp_SetSystemVSyncCount2(int systemVSyncCount); + +/// OVRPlugin does not support Android system callbacks in UE4. +/// Please use Optional Mobile Features Blueprint Library Plugin or include FAndroidMisc.h in your project +#ifndef OVRPLUGIN_JNI_LIB_EXCLUDED +/// Gets the current system volume level. +OVRP_EXPORT ovrpResult ovrp_GetSystemVolume2(float* systemVolume); + +/// If true, headphones are currently attached to the device. +OVRP_EXPORT ovrpResult ovrp_GetSystemHeadphonesPresent2(ovrpBool* systemHeadphonesPresent); + +/// Gets the status of the system's battery or "Unknown" if there is none. +OVRP_EXPORT ovrpResult ovrp_GetSystemBatteryStatus2(ovrpBatteryStatus* systemBatteryStatus); + +/// Gets the current available battery charge, ranging from 0 (empty) to 1 (full). +OVRP_EXPORT ovrpResult ovrp_GetSystemBatteryLevel2(float* systemBatteryLevel); + +/// Gets the current battery temperature in degrees Celsius. +OVRP_EXPORT ovrpResult ovrp_GetSystemBatteryTemperature2(float* systemBatteryTemperature); +#endif + +/// Gets the current product name for the device, if available. +OVRP_EXPORT ovrpResult ovrp_GetSystemProductName2(char const** systemProductName); + +/// Gets the current region for the device, if available. +OVRP_EXPORT ovrpResult ovrp_GetSystemRegion2(ovrpSystemRegion* systemRegion); + +/// Shows a given platform user interface. +OVRP_EXPORT ovrpResult ovrp_ShowSystemUI2(ovrpUI ui); + +/// If true, the app has VR focus. +OVRP_EXPORT ovrpResult ovrp_GetAppHasVrFocus2(ovrpBool* appHasVrFocus); + +/// True if the application is the foreground application and receives input (e.g. Touch +/// controller state). If this is false then the application is in the background (but possibly +/// still visible) should hide any input representations such as hands. +OVRP_EXPORT ovrpResult ovrp_GetAppHasInputFocus(ovrpBool* appHasInputFocus); + +/// True if a system overlay is present, such as a dashboard. In this case the application +/// (if visible) should pause while still drawing, avoid drawing near-field graphics so they +/// don't visually fight with the system overlay, and consume fewer CPU and GPU resources. +OVRP_EXPORT ovrpResult ovrp_GetAppHasSystemOverlayPresent(ovrpBool* appHasOverlayPresent); + +/// If true, the app should quit as soon as possible. +OVRP_EXPORT ovrpResult ovrp_GetAppShouldQuit2(ovrpBool* appShouldQuit); + +/// If true, the app should recenter as soon as possible. +OVRP_EXPORT ovrpResult ovrp_GetAppShouldRecenter2(ovrpBool* appShouldRecenter); + +/// If true, the app should recreate the distortion window as soon as possible. +OVRP_EXPORT ovrpResult ovrp_GetAppShouldRecreateDistortionWindow2(ovrpBool* appShouldRecreateDistortionWindow); + +/// Gets the latest measured latency timings. +OVRP_EXPORT ovrpResult ovrp_GetAppLatencyTimings2(ovrpAppLatencyTimings* appLatencyTimings); + +/// Sets the engine info for the current app. +OVRP_EXPORT ovrpResult ovrp_SetAppEngineInfo2(const char* engineName, const char* engineVersion, ovrpBool isEditor); + +/// If true, the user is currently wearing the VR display and it is not idle. +OVRP_EXPORT ovrpResult ovrp_GetUserPresent2(ovrpBool* userPresent); + +/// Gets the physical inter-pupillary distance (IPD) separating the user's eyes in meters. +OVRP_EXPORT ovrpResult ovrp_GetUserIPD2(float* userIPD); + +/// Sets the physical inter-pupillary distance (IPD) separating the user's eyes in meters. +OVRP_EXPORT ovrpResult ovrp_SetUserIPD2(float value); + +/// Gets the physical height of the player's eyes from the ground in meters. +OVRP_EXPORT ovrpResult ovrp_GetUserEyeHeight2(float* userEyeHeight); + +/// Sets the physical height of the player's eyes from the ground in meters. +OVRP_EXPORT ovrpResult ovrp_SetUserEyeHeight2(float userEyeHeight); + +/// Gets the physical distance from the base of the neck to the center of the player's eyes in +/// meters. +OVRP_EXPORT ovrpResult ovrp_GetUserNeckEyeDistance2(ovrpVector2f* userEyeNeckDistance); + +/// Sets the physical distance from the base of the neck to the center of the player's eyes in +/// meters. +OVRP_EXPORT ovrpResult ovrp_SetUserNeckEyeDistance2(ovrpVector2f userEyeNeckDistance); + +/// Setup the current display objects +OVRP_EXPORT ovrpResult ovrp_SetupDisplayObjects2(void* device, void* display, void* window); + +/// Return true if the device supports multi-view rendering +OVRP_EXPORT ovrpResult ovrp_GetSystemMultiViewSupported2(ovrpBool* systemMultiViewSupported); + +/// Return true is the plugin supports submitting texture arrays +OVRP_EXPORT ovrpResult ovrp_GetEyeTextureArraySupported2(ovrpBool* eyeTextureArraySupported); + +/// If true, the boundary system is configured with valid boundary data. +OVRP_EXPORT ovrpResult ovrp_GetBoundaryConfigured2(ovrpBool* boundaryConfigured); + +/// Return success if the device supports depth compositing +OVRP_EXPORT ovrpResult ovrp_GetDepthCompositingSupported(ovrpBool* depthCompositingSupported); + +/// Performs a boundary test between the specified node and boundary types. +OVRP_EXPORT ovrpResult +ovrp_TestBoundaryNode2(ovrpNode node, ovrpBoundaryType boundaryType, ovrpBoundaryTestResult* boundaryTestResult); + +/// Performs a boundary test between the specified point and boundary types. +OVRP_EXPORT ovrpResult +ovrp_TestBoundaryPoint2(ovrpVector3f point, ovrpBoundaryType boundaryType, ovrpBoundaryTestResult* boundaryTestResult); + +/// Gets the geometry data for the specified boundary type. +OVRP_EXPORT ovrpResult ovrp_GetBoundaryGeometry3(ovrpBoundaryType boundaryType, ovrpVector3f* points, int* pointsCount); + +/// Gets the dimensions for the specified boundary type. Returned x,y,z values correspond to width, +/// height, depth. +OVRP_EXPORT ovrpResult ovrp_GetBoundaryDimensions2(ovrpBoundaryType boundaryType, ovrpVector3f* bounaryDimensions); + +/// Gets the current visiblity status for the boundary system. +OVRP_EXPORT ovrpResult ovrp_GetBoundaryVisible2(ovrpBool* boundaryVisible); + +/// Requests that the boundary system visibility be set to the specified value. Can be overridden by +/// the boundary system or the user. +OVRP_EXPORT ovrpResult ovrp_SetBoundaryVisible2(ovrpBool boundaryVisible); + +/// Returns the currently present headset type. +OVRP_EXPORT ovrpResult ovrp_GetSystemHeadsetType2(ovrpSystemHeadset* systemHeadsetType); + +/// Returns information useful for performance analysis and dynamic quality adjustments. +OVRP_EXPORT ovrpResult ovrp_GetAppPerfStats2(ovrpAppPerfStats* appPerfStats); + +/// Resets internal performance counters to clear previous data from impacting the current reported +/// state. +OVRP_EXPORT ovrpResult ovrp_ResetAppPerfStats2(); + +/// Return the app FPS, thread safe +OVRP_EXPORT ovrpResult ovrp_GetAppFramerate2(float* appFramerate); + +/// Returns if a certain perf metrics is supported +OVRP_EXPORT ovrpResult ovrp_IsPerfMetricsSupported(ovrpPerfMetrics perfMetrics, ovrpBool* supported); + +/// Returns if a floating point perf metrics +OVRP_EXPORT ovrpResult ovrp_GetPerfMetricsFloat(ovrpPerfMetrics perfMetrics, float* value); + +/// Returns if an integer perf metrics +OVRP_EXPORT ovrpResult ovrp_GetPerfMetricsInt(ovrpPerfMetrics perfMetrics, int* value); + +/// Set a latency when getting the hand node poses through ovrp_GetNodePoseState2(ovrpStep_Render, ...) +OVRP_EXPORT ovrpResult ovrp_SetHandNodePoseStateLatency(double latencyInSeconds); + +/// Get the current latency when getting the hand node poses through ovrp_GetNodePoseState2(ovrpStep_Render, ...) +OVRP_EXPORT ovrpResult ovrp_GetHandNodePoseStateLatency(double* latencyInSeconds); + +/// Returns the recommended multisample antialiasing level for the current device. +OVRP_EXPORT ovrpResult ovrp_GetSystemRecommendedMSAALevel2(int* systemRecommendedMSAALevel); + +/// Inhibits system UX behavior. +OVRP_EXPORT ovrpResult ovrp_SetInhibitSystemUX2(ovrpBool inhibitSystemUX); + +/// Return true if the device supports tiled multires +OVRP_EXPORT ovrpResult ovrp_GetTiledMultiResSupported(ovrpBool* foveationSupported); + +/// Returns the current multires level on the device +OVRP_EXPORT ovrpResult ovrp_GetTiledMultiResLevel(ovrpTiledMultiResLevel* level); + +/// Sets MultiRes levels +OVRP_EXPORT ovrpResult ovrp_SetTiledMultiResLevel(ovrpTiledMultiResLevel level); + +/// Return if MultiRes is dynamic or not +OVRP_EXPORT ovrpResult ovrp_GetTiledMultiResDynamic(ovrpBool* isDynamic); + +/// Sets if MultiRes is dynamic or not +OVRP_EXPORT ovrpResult ovrp_SetTiledMultiResDynamic(ovrpBool isDynamic); + +/// Return true if the device supports eye tracked foveation +OVRP_EXPORT ovrpResult ovrp_GetFoveationEyeTrackedSupported(ovrpBool* foveationSupported); + +/// Return if foveation is eye tracked +OVRP_EXPORT ovrpResult ovrp_GetFoveationEyeTracked(ovrpBool* isEyeTracked); + +/// Sets if foveation is eye tracked +OVRP_EXPORT ovrpResult ovrp_SetFoveationEyeTracked(ovrpBool isEyeTracked); + +/// Gets the eye tracked foveation center (used for tile offset) +OVRP_EXPORT ovrpResult ovrp_GetFoveationEyeTrackedCenter(ovrpVector2f fovCenter[2]); + + + + + + + + + + + + +/// Return true if the device supports GPU Util querying +OVRP_EXPORT ovrpResult ovrp_GetGPUUtilSupported(ovrpBool* gpuUtilSupported); + +/// Return the GPU util if the device supports it +OVRP_EXPORT ovrpResult ovrp_GetGPUUtilLevel(float* gpuUtil); + +/// Set thread's performance level, for example, put the performance critical thread on golden cores, +/// future policy might change for future hardware +OVRP_EXPORT ovrpResult ovrp_SetThreadPerformance(int threadId, ovrpThreadPerf perf); + +/// This is specifically for Unity to fix Core Affinity wrong assignment. +OVRP_EXPORT ovrpResult ovrp_AutoThreadScheduling( + unsigned int bigCoreMaskFromEngine, + unsigned int* threadIds, + ovrpThreadPerf* threadPerfFlags, + int threadCount); + +OVRP_EXPORT ovrpResult ovrp_GetGPUFrameTime(float* gpuTime); + +/// This is to request vertices and indices for the triangle mesh +OVRP_EXPORT ovrpResult ovrp_GetViewportStencil( + ovrpEye eyeId, + ovrpViewportStencilType type, + ovrpVector2f* vertices, + int* vertexCount, + ovrpUInt16* indices, + int* indexCount); + +OVRP_EXPORT ovrpResult ovrp_SendEvent(const char* eventName, const char* param); + +OVRP_EXPORT ovrpResult ovrp_SendEvent2(const char* eventName, const char* param, const char* source); + +OVRP_EXPORT ovrpResult ovrp_AddCustomMetadata(const char* metadataName, const char* metadataParam); + +OVRP_EXPORT ovrpResult ovrp_SetDeveloperMode(ovrpBool active); + +OVRP_EXPORT ovrpResult ovrp_SetDeveloperModeStrict(ovrpBool active); + +OVRP_EXPORT ovrpResult ovrp_SetVrApiPropertyInt(int propertyEnum, int value); + +OVRP_EXPORT ovrpResult ovrp_SetVrApiPropertyFloat(int propertyEnum, float value); + +OVRP_EXPORT ovrpResult ovrp_GetVrApiPropertyInt(int propertyEnum, int* value); + + + + + + + +OVRP_EXPORT ovrpResult ovrp_GetCurrentTrackingTransformPose(ovrpPosef* trackingTransformPose); + +OVRP_EXPORT ovrpResult ovrp_GetTrackingTransformRawPose(ovrpPosef* trackingTransformRawPose); + +OVRP_EXPORT ovrpResult +ovrp_GetTrackingTransformRelativePose(ovrpPosef* trackingTransformRelativePose, ovrpTrackingOrigin trackingOrigin); + +OVRP_EXPORT ovrpResult ovrp_GetTimeInSeconds(double* timeInSeconds); + +OVRP_EXPORT ovrpResult ovrp_GetASWVelocityScale(float* aswVelocityScale); +OVRP_EXPORT ovrpResult ovrp_GetASWDepthScale(float* aswDepthScale); +OVRP_EXPORT ovrpResult ovrp_GetASWAdaptiveMode(ovrpBool* aswAdaptiveMode); +OVRP_EXPORT ovrpResult ovrp_SetASWAdaptiveMode(ovrpBool aswAdaptiveMode); +OVRP_EXPORT ovrpResult ovrp_IsRequestingASWData(ovrpBool* needASWData); + +OVRP_EXPORT ovrpResult ovrp_GetPredictedDisplayTime(int frameIndex, double* predictedDisplayTime); + +OVRP_EXPORT ovrpResult ovrp_GetHandTrackingEnabled(ovrpBool* handTrackingEnabled); +OVRP_EXPORT ovrpResult ovrp_GetHandState(ovrpStep step, ovrpHand hand, ovrpHandState* handState); +OVRP_EXPORT ovrpResult ovrp_GetHandState2(ovrpStep step, int frameIndex, ovrpHand hand, ovrpHandState* handState); + + + + +OVRP_EXPORT ovrpResult ovrp_GetSkeleton2(ovrpSkeletonType skeletonType, ovrpSkeleton2* skeleton); +OVRP_EXPORT ovrpResult ovrp_GetMesh(ovrpMeshType meshType, ovrpMesh* mesh); + +OVRP_EXPORT ovrpResult ovrp_GetBodyState(ovrpStep step, int frameIndex, ovrpBodyState* bodyState); +OVRP_EXPORT ovrpResult ovrp_GetBodyTrackingEnabled(ovrpBool* enabled); +OVRP_EXPORT ovrpResult ovrp_GetBodyTrackingSupported(ovrpBool* supported); + +OVRP_EXPORT ovrpResult ovrp_StartFaceTracking(); +OVRP_EXPORT ovrpResult ovrp_StopFaceTracking(); +OVRP_EXPORT ovrpResult ovrp_StartBodyTracking(); +OVRP_EXPORT ovrpResult ovrp_StopBodyTracking(); +OVRP_EXPORT ovrpResult ovrp_StartEyeTracking(); +OVRP_EXPORT ovrpResult ovrp_StopEyeTracking(); + +OVRP_EXPORT ovrpResult ovrp_GetLocalTrackingSpaceRecenterCount(int* recenterCount); + +// Returns true if the system Hmd is in 3dof mode +OVRP_EXPORT ovrpResult ovrp_GetSystemHmd3DofModeEnabled(ovrpBool* enabled); + +OVRP_EXPORT ovrpResult ovrp_SetClientColorDesc(ovrpColorSpace colorSpace); +OVRP_EXPORT ovrpResult ovrp_GetHmdColorDesc(ovrpColorSpace* colorSpace); + +// app should call this in a loop until there are no more events, which will return ovrpSuccess_EventUnavailable and an +// event of type ovrpEventType_None ovrp_PollEvent and ovrp_PollEvent2 are both in use, Unity needed ovrp_PollEvent2 due +// to memory allocation issues +OVRP_EXPORT ovrpResult ovrp_PollEvent(ovrpEventDataBuffer* eventBuffer); +OVRP_EXPORT ovrpResult ovrp_PollEvent2(ovrpEventType* eventType, unsigned char** eventBuffer); + +OVRP_EXPORT ovrpResult ovrp_SetKeyboardOverlayUV(ovrpVector2f uv); +OVRP_EXPORT ovrpResult ovrp_SetKeyboardOverlayPose(ovrpPosef pose); + + + + + + + +OVRP_EXPORT ovrpResult ovrp_StartKeyboardTracking(ovrpUInt64 trackedKeyboardId); +OVRP_EXPORT ovrpResult ovrp_StopKeyboardTracking(); +OVRP_EXPORT ovrpResult ovrp_GetKeyboardState(ovrpStep step, int frameIndex, ovrpKeyboardState* keyboardState); +OVRP_EXPORT ovrpResult ovrp_GetSystemKeyboardDescription( + ovrpTrackedKeyboardQueryFlags queryFlags, + ovrpKeyboardDescription* keyboardDescription); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +/// Gets the current recent pose, acceleration, and velocity of the given node for the current time without any +/// prediction +OVRP_EXPORT ovrpResult ovrp_GetNodePoseStateImmediate(ovrpNode nodeId, ovrpPoseStatef* nodePoseState); + +OVRP_EXPORT ovrpResult ovrp_GetNodePoseStateAtTime(double time, ovrpNode nodeId, ovrpPoseStatef* pose); + +OVRP_EXPORT ovrpResult ovrp_GetRenderModelPaths(unsigned int index, char* path); +OVRP_EXPORT ovrpResult ovrp_GetRenderModelProperties(const char* path, ovrpRenderModelProperties* properties); +OVRP_EXPORT ovrpResult ovrp_GetRenderModelProperties2( + const char* path, + ovrpRenderModelFlags renderModelFlags, + ovrpRenderModelProperties* properties); +OVRP_EXPORT ovrpResult ovrp_LoadRenderModel( + ovrpUInt64 modelKey, + ovrpUInt32 bufferInputCapacity, + ovrpUInt32* bufferCountOutput, + unsigned char* buffer); + +OVRP_EXPORT ovrpResult ovrp_LocateSpace(ovrpPosef* location, const ovrpSpace* space, ovrpTrackingOrigin baseSpaceType); +OVRP_EXPORT ovrpResult +ovrp_LocateSpace2(ovrpSpaceLocationf* location, const ovrpSpace* space, ovrpTrackingOrigin baseSpaceType); +OVRP_EXPORT ovrpResult ovrp_CreateSpatialAnchor(const ovrpSpatialAnchorCreateInfo* createInfo, ovrpUInt64* requestId); +OVRP_EXPORT ovrpResult ovrp_DestroySpace(ovrpSpace* space); +OVRP_EXPORT ovrpResult ovrp_SetSpaceComponentStatus( + const ovrpSpace* space, + ovrpSpaceComponentType componentType, + const ovrpBool enable, + const double timeout, + ovrpUInt64* requestId); +OVRP_EXPORT ovrpResult ovrp_GetSpaceComponentStatus( + const ovrpSpace* space, + ovrpSpaceComponentType componentType, + ovrpBool* enabled, + ovrpBool* changePending); +OVRP_EXPORT ovrpResult ovrp_EnumerateSpaceSupportedComponents( + const ovrpSpace* space, + ovrpUInt32 componentTypesCapacityInput, + ovrpUInt32* componentTypesCountOutput, + ovrpSpaceComponentType* componentTypes); +OVRP_EXPORT ovrpResult ovrp_QuerySpaces(const ovrpSpaceQueryInfo* queryInfo, ovrpUInt64* requestId); +OVRP_EXPORT ovrpResult ovrp_RetrieveSpaceQueryResults( + ovrpUInt64* requestId, + ovrpUInt32 resultCapacityInput, + ovrpUInt32* resultCountOutput, + ovrpSpaceQueryResult* results); +OVRP_EXPORT ovrpResult ovrp_SaveSpace( + const ovrpSpace* space, + ovrpSpaceStorageLocation location, + ovrpSpaceStoragePersistenceMode mode, + ovrpUInt64* requestId); +OVRP_EXPORT ovrpResult +ovrp_EraseSpace(const ovrpSpace* space, ovrpSpaceStorageLocation location, ovrpUInt64* requestId); +OVRP_EXPORT ovrpResult ovrp_GetSpaceUuid(const ovrpSpace* space, ovrpUuid* uuid); +OVRP_EXPORT ovrpResult ovrp_GetSpaceUserId(const ovrpUser* spaceUser, ovrpUInt64* spaceUserId); +OVRP_EXPORT ovrpResult ovrp_CreateSpaceUser(const ovrpUInt64* spaceUserId, ovrpUser* spaceUser); +OVRP_EXPORT ovrpResult ovrp_DestroySpaceUser(const ovrpUser* spaceUser); +OVRP_EXPORT ovrpResult ovrp_SaveSpaceList( + const ovrpSpace* spaces, + ovrpUInt32 numSpaces, + ovrpSpaceStorageLocation location, + ovrpUInt64* requestId); +OVRP_EXPORT ovrpResult ovrp_ShareSpaces( + const ovrpSpace* spaces, + ovrpUInt32 numSpaces, + const ovrpUser* users, + ovrpUInt32 numUsers, + ovrpUInt64* requestId); + +OVRP_EXPORT ovrpResult ovrp_GetSpaceContainer(const ovrpSpace* space, ovrpSpaceContainer* container); + +OVRP_EXPORT ovrpResult ovrp_GetSpaceBoundingBox2D(const ovrpSpace* space, ovrpRectf* rect); + +OVRP_EXPORT ovrpResult ovrp_GetSpaceBoundingBox3D(const ovrpSpace* space, ovrpBoundsf* bounds); + +OVRP_EXPORT ovrpResult ovrp_GetSpaceSemanticLabels(const ovrpSpace* space, ovrpSemanticLabels* labels); + +OVRP_EXPORT ovrpResult ovrp_GetSpaceRoomLayout(const ovrpSpace* space, ovrpRoomLayout* layout); + +OVRP_EXPORT ovrpResult ovrp_GetSpaceBoundary2D(const ovrpSpace* space, ovrpBoundary2D* boundary); + +OVRP_EXPORT ovrpResult ovrp_RequestSceneCapture(const ovrpSceneCaptureRequest* request, ovrpUInt64* requestId); + + + + + + + + + + + +OVRP_EXPORT ovrpResult ovrp_GetFaceTrackingEnabled(ovrpBool* faceTrackingEnabled); + +OVRP_EXPORT ovrpResult ovrp_GetFaceTrackingSupported(ovrpBool* faceTrackingSupported); + +OVRP_EXPORT ovrpResult ovrp_GetFaceState(ovrpStep step, int frameIndex, ovrpFaceState* faceState); + +OVRP_EXPORT ovrpResult ovrp_GetEyeTrackingEnabled(ovrpBool* eyeTrackingEnabled); + +OVRP_EXPORT ovrpResult ovrp_GetEyeTrackingSupported(ovrpBool* eyeTrackingSupported); + +OVRP_EXPORT ovrpResult ovrp_GetEyeGazesState(ovrpStep step, int frameIndex, ovrpEyeGazesState* eyeGazesState); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +OVRP_EXPORT ovrpResult +ovrp_FeatureFidelitySetFeatureEnable(ovrpFeatureType feature, ovrpFeatureEnableState featureEnableState); +OVRP_EXPORT ovrpResult +ovrp_FeatureFidelitySetFeatureFidelity(ovrpFeatureType feature, ovrpFeatureFidelity featureFidelity); +OVRP_EXPORT ovrpResult ovrp_FeatureFidelityGetFeatureState( + ovrpFeatureType feature, + ovrpFeatureState* outIdealState, + ovrpFeatureState* outCurrentState); + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +OVRP_EXPORT ovrpResult ovrp_GetLocalDimmingSupported(ovrpBool* localDimmingSupported); +OVRP_EXPORT ovrpResult ovrp_SetLocalDimming(ovrpBool localDimmingMode); +OVRP_EXPORT ovrpResult ovrp_GetLocalDimming(ovrpBool* localDimmingMode); + +OVRP_EXPORT ovrpResult ovrp_GetCurrentInteractionProfile(ovrpHand hand, ovrpInteractionProfile* interactionProfile); + + + + + + + + + + + + + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Deprecated.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Deprecated.h new file mode 100644 index 0000000000000000000000000000000000000000..5e35ad8a076018f6f85c776b9f2b5e5953b3b078 --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Deprecated.h @@ -0,0 +1,430 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVR_Plugin_Deprecated_h +#define OVR_Plugin_Deprecated_h + +#include "OVR_Plugin.h" +#include "OVR_Plugin_Types_Deprecated.h" + +#ifdef __cplusplus +extern "C" { +#endif + +// Deprecated to avoid an extra shutdown/initialize on the mobile side +OVRP_EXPORT ovrpResult ovrp_PreInitialize3(void* activity); + +// Deprecated for adding an ovrpPreinitializeFlags parameter +OVRP_EXPORT ovrpResult ovrp_PreInitialize4(void* activity, ovrpRenderAPIType apiType); + +// Deprecated for getting extra Vulkan paramters from Unity Oculus XR Plugin +OVRP_EXPORT ovrpResult ovrp_Initialize6( + ovrpRenderAPIType apiType, + ovrpLogCallback logCallback, + void* activity, + void* vkInstance, + void* vkPhysicalDevice, + void* vkDevice, + void* vkQueue, + void* vkGetInstanceProcAddr, // PFN_vkGetInstanceProcAddr + unsigned int vkQueueFamilyIndex, + int initializeFlags, + OVRP_CONSTREF(ovrpVersion) version); + +OVRP_EXPORT ovrpResult ovrp_Initialize5( + ovrpRenderAPIType apiType, + ovrpLogCallback logCallback, + void* activity, + void* vkInstance, + void* vkPhysicalDevice, + void* vkDevice, + void* vkQueue, + int initializeFlags, + OVRP_CONSTREF(ovrpVersion) version); + +// Deprecated by VRAPI_Vulkan changes +OVRP_EXPORT ovrpResult ovrp_PreInitialize2(); + +OVRP_EXPORT ovrpResult ovrp_Initialize4( + ovrpRenderAPIType apiType, + ovrpLogCallback logCallback, + void* activity, + void* instance, + int initializeFlags, + OVRP_CONSTREF(ovrpVersion) version); + +// Deprecated by CAPI_Vulkan changes +OVRP_EXPORT ovrpResult ovrp_Initialize3( + ovrpRenderAPIType apiType, + ovrpLogCallback logCallback, + void* activity, + int initializeFlags, + OVRP_CONSTREF(ovrpVersion)); + +OVRP_EXPORT ovrpResult ovrp_BeginFrame3(int frameIndex); + +OVRP_EXPORT ovrpResult +ovrp_EndFrame3(int frameIndex, ovrpLayerSubmit const* const* layerSubmitPtrs, int layerSubmitCount); + +// Deprecated by WaitToBeginFrame/BeginFrame/EndFrame changes +OVRP_EXPORT ovrpResult ovrp_BeginFrame2(int frameIndex); + +OVRP_EXPORT ovrpResult +ovrp_EndFrame2(int frameIndex, ovrpLayerSubmit const* const* layerSubmitPtrs, int layerSubmitCount); + +// Deprecated by ovrpResult changes +OVRP_EXPORT ovrpBool ovrp_PreInitialize(); +OVRP_EXPORT ovrpBool ovrp_Shutdown(); +OVRP_EXPORT const char* ovrp_GetVersion(); +OVRP_EXPORT const char* ovrp_GetNativeSDKVersion(); +OVRP_EXPORT void* ovrp_GetNativeSDKPointer(); +OVRP_EXPORT const void* ovrp_GetDisplayAdapterId(); +OVRP_EXPORT const void* ovrp_GetAudioOutId(); +OVRP_EXPORT const void* ovrp_GetAudioOutDeviceId(); +OVRP_EXPORT const void* ovrp_GetAudioInId(); +OVRP_EXPORT const void* ovrp_GetAudioInDeviceId(); +OVRP_EXPORT ovrpBool ovrp_SetupDistortionWindow2(int flags); +OVRP_EXPORT ovrpBool ovrp_DestroyDistortionWindow(); + +OVRP_EXPORT ovrpBool +ovrp_SetupMirrorTexture(void* device, int height, int width, ovrpTextureFormat format, ovrpTextureHandle* result); + +OVRP_EXPORT ovrpBool ovrp_DestroyMirrorTexture(); +OVRP_EXPORT float ovrp_GetAdaptiveGpuPerformanceScale(); +OVRP_EXPORT float ovrp_GetAppCpuStartToGpuEndTime(); +OVRP_EXPORT ovrpVector2f ovrp_GetEyePixelsPerTanAngleAtCenter(int eyeIndex); +OVRP_EXPORT ovrpVector3f ovrp_GetHmdToEyeOffset(int eyeIndex); +OVRP_EXPORT ovrpBool ovrp_Update2(ovrpStep step, int frameIndex, double predictionSeconds); +OVRP_EXPORT ovrpBool ovrp_BeginFrame(int frameIndex); +OVRP_EXPORT ovrpBool ovrp_GetTrackingOrientationSupported(); +OVRP_EXPORT ovrpBool ovrp_GetTrackingOrientationEnabled(); +OVRP_EXPORT ovrpBool ovrp_SetTrackingOrientationEnabled(ovrpBool value); +OVRP_EXPORT ovrpBool ovrp_GetTrackingPositionSupported(); +OVRP_EXPORT ovrpBool ovrp_GetTrackingPositionEnabled(); +OVRP_EXPORT ovrpBool ovrp_SetTrackingPositionEnabled(ovrpBool value); +OVRP_EXPORT ovrpBool ovrp_GetTrackingIPDEnabled(); +OVRP_EXPORT ovrpBool ovrp_SetTrackingIPDEnabled(ovrpBool value); +OVRP_EXPORT ovrpPosef ovrp_GetTrackingCalibratedOrigin(); +OVRP_EXPORT ovrpBool ovrpi_SetTrackingCalibratedOrigin(); +OVRP_EXPORT ovrpTrackingOrigin ovrp_GetTrackingOriginType(); +OVRP_EXPORT ovrpBool ovrp_SetTrackingOriginType(ovrpTrackingOrigin originType); +OVRP_EXPORT ovrpBool ovrp_RecenterTrackingOrigin(unsigned int flags); +OVRP_EXPORT ovrpBool ovrp_GetNodePresent(ovrpNode nodeId); +OVRP_EXPORT ovrpBool ovrp_GetNodeOrientationTracked(ovrpNode nodeId); +OVRP_EXPORT ovrpBool ovrp_GetNodePositionTracked(ovrpNode nodeId); +OVRP_EXPORT ovrpBool ovrp_SetNodePositionTracked(ovrpNode nodeId, ovrpBool tracked); +OVRP_EXPORT ovrpPoseStatef ovrp_GetNodePoseState(ovrpStep step, ovrpNode nodeId); +OVRP_EXPORT ovrpControllerState ovrp_GetControllerState(ovrpController controllerMask); +OVRP_EXPORT ovrpControllerState2 ovrp_GetControllerState2(ovrpController controllerMask); +OVRP_EXPORT ovrpResult ovrp_GetControllerState3(ovrpController controllerMask, ovrpControllerState2* controllerState); +OVRP_EXPORT ovrpController ovrp_GetActiveController(); +OVRP_EXPORT ovrpController ovrp_GetConnectedControllers(); + +OVRP_EXPORT ovrpBool ovrp_SetControllerVibration(ovrpController controllerMask, float frequency, float amplitude); + +OVRP_EXPORT ovrpHapticsDesc ovrp_GetControllerHapticsDesc(ovrpController controllerMask); +OVRP_EXPORT ovrpHapticsState ovrp_GetControllerHapticsState(ovrpController controllerMask); + +OVRP_EXPORT ovrpBool ovrp_SetControllerHaptics(ovrpController controllerMask, ovrpHapticsBuffer hapticsBuffer); + +OVRP_EXPORT int ovrp_GetSystemCpuLevel(); +OVRP_EXPORT ovrpBool ovrp_SetSystemCpuLevel(int value); +OVRP_EXPORT ovrpBool ovrp_SetAppCPUPriority(ovrpBool priority); +OVRP_EXPORT ovrpBool ovrp_GetAppCPUPriority(); +OVRP_EXPORT int ovrp_GetSystemGpuLevel(); +OVRP_EXPORT ovrpBool ovrp_SetSystemGpuLevel(int value); +OVRP_EXPORT ovrpBool ovrp_GetSystemPowerSavingMode(); +OVRP_EXPORT float ovrp_GetSystemDisplayFrequency(); +OVRP_EXPORT int ovrp_GetSystemVSyncCount(); +OVRP_EXPORT ovrpBool ovrp_SetSystemVSyncCount(int value); +OVRP_EXPORT float ovrp_GetSystemVolume(); +OVRP_EXPORT ovrpBool ovrp_GetSystemHeadphonesPresent(); +OVRP_EXPORT ovrpBatteryStatus ovrp_GetSystemBatteryStatus(); +OVRP_EXPORT float ovrp_GetSystemBatteryLevel(); +OVRP_EXPORT float ovrp_GetSystemBatteryTemperature(); +OVRP_EXPORT const char* ovrp_GetSystemProductName(); +OVRP_EXPORT ovrpSystemRegion ovrp_GetSystemRegion(); +OVRP_EXPORT ovrpBool ovrp_ShowSystemUI(ovrpUI ui); +OVRP_EXPORT ovrpBool ovrp_GetAppHasVrFocus(); +OVRP_EXPORT ovrpBool ovrp_GetAppShouldQuit(); +OVRP_EXPORT ovrpBool ovrp_GetAppShouldRecenter(); +OVRP_EXPORT ovrpBool ovrp_GetAppShouldRecreateDistortionWindow(); +OVRP_EXPORT const char* ovrp_GetAppLatencyTimings(); + +OVRP_EXPORT ovrpBool ovrp_SetAppEngineInfo(const char* engineName, const char* engineVersion, ovrpBool isEditor); + +OVRP_EXPORT ovrpBool ovrp_GetUserPresent(); +OVRP_EXPORT float ovrp_GetUserIPD(); +OVRP_EXPORT ovrpBool ovrp_SetUserIPD(float value); +OVRP_EXPORT float ovrp_GetUserEyeHeight(); +OVRP_EXPORT ovrpBool ovrp_SetUserEyeHeight(float value); +OVRP_EXPORT ovrpVector2f ovrp_GetUserNeckEyeDistance(); +OVRP_EXPORT ovrpBool ovrp_SetUserNeckEyeDistance(ovrpVector2f value); +OVRP_EXPORT ovrpBool ovrp_SetupDisplayObjects(void* device, void* display, void* window); +OVRP_EXPORT ovrpBool ovrp_GetSystemMultiViewSupported(); +OVRP_EXPORT ovrpBool ovrp_GetEyeTextureArraySupported(); +OVRP_EXPORT ovrpBool ovrp_GetBoundaryConfigured(); + +OVRP_EXPORT ovrpBoundaryTestResult ovrp_TestBoundaryNode(ovrpNode node, ovrpBoundaryType boundaryType); + +OVRP_EXPORT ovrpBoundaryTestResult ovrp_TestBoundaryPoint(ovrpVector3f point, ovrpBoundaryType boundaryType); + +OVRP_EXPORT ovrpBool ovrp_GetBoundaryGeometry2(ovrpBoundaryType boundaryType, ovrpVector3f* points, int* pointsCount); + +OVRP_EXPORT ovrpVector3f ovrp_GetBoundaryDimensions(ovrpBoundaryType boundaryType); +OVRP_EXPORT ovrpBool ovrp_GetBoundaryVisible(); +OVRP_EXPORT ovrpBool ovrp_SetBoundaryVisible(ovrpBool value); +OVRP_EXPORT ovrpSystemHeadset ovrp_GetSystemHeadsetType(); +OVRP_EXPORT ovrpAppPerfStats ovrp_GetAppPerfStats(); +OVRP_EXPORT ovrpBool ovrp_ResetAppPerfStats(); +OVRP_EXPORT float ovrp_GetAppFramerate(); +OVRP_EXPORT int ovrp_GetSystemRecommendedMSAALevel(); +OVRP_EXPORT ovrpBool ovrp_SetInhibitSystemUX(ovrpBool value); +OVRP_EXPORT ovrpBool ovrp_SetDebugDumpEnabled(ovrpBool value); + +// Deprecated by UE4 integration changes +OVRP_EXPORT ovrpBool +ovrp_Initialize2(ovrpRenderAPIType apiType, ovrpLogCallback logCallback, ovrpBool supportsMixedRendering); + +OVRP_EXPORT ovrpBool ovrp_SetupDistortionWindow(); + +OVRP_EXPORT ovrpBool ovrp_SetupEyeTexture2( + ovrpEye eyeId, + int stage, + void* device, + int height, + int width, + int samples, + ovrpTextureFormat format, + void* result); + +OVRP_EXPORT ovrpBool ovrp_DestroyEyeTexture(ovrpEye eyeId, int stage); +OVRP_EXPORT ovrpSizei ovrp_GetEyeTextureSize(ovrpEye eyeId); +OVRP_EXPORT int ovrp_GetEyeTextureStageCount(); +OVRP_EXPORT float ovrp_GetEyeRecommendedResolutionScale(); +OVRP_EXPORT ovrpBool ovrp_GetEyeTextureFlippedY(); +OVRP_EXPORT ovrpBool ovrp_SetEyeTextureFlippedY(ovrpBool value); +OVRP_EXPORT ovrpBool ovrp_GetEyeTextureShared(); +OVRP_EXPORT ovrpBool ovrp_SetEyeTextureShared(ovrpBool value); +OVRP_EXPORT float ovrp_GetEyeTextureScale(); +OVRP_EXPORT ovrpBool ovrp_SetEyeTextureScale(float value); +OVRP_EXPORT float ovrp_GetEyeViewportScale(); +OVRP_EXPORT ovrpBool ovrp_SetEyeViewportScale(float value); + +OVRP_EXPORT ovrpBool ovrp_GetEyeOcclusionMesh(int eyeIndex, float** vertices, int** indices, int* indexCount); + +OVRP_EXPORT ovrpBool ovrp_GetEyeOcclusionMeshEnabled(); +OVRP_EXPORT ovrpBool ovrp_SetEyeOcclusionMeshEnabled(ovrpBool value); +OVRP_EXPORT ovrpTextureFormat ovrp_GetDesiredEyeTextureFormat(); +OVRP_EXPORT ovrpBool ovrp_SetDesiredEyeTextureFormat(ovrpTextureFormat value); +OVRP_EXPORT ovrpBool ovrp_GetEyePreviewRect(int eyeIndex, ovrpRecti* outputRect); +OVRP_EXPORT ovrpBool ovrp_GetAppChromaticCorrection(); +OVRP_EXPORT ovrpBool ovrp_SetAppChromaticCorrection(ovrpBool value); +OVRP_EXPORT ovrpResult ovrp_GetReorientHMDOnControllerRecenter(ovrpBool* recenter); +OVRP_EXPORT ovrpResult ovrp_SetReorientHMDOnControllerRecenter(ovrpBool recenter); +OVRP_EXPORT ovrpBool ovrp_EndEye(ovrpEye eye); +OVRP_EXPORT ovrpBool ovrp_EndFrame(int frameIndex); +OVRP_EXPORT ovrpBool ovrpi_SetTrackingCalibratedOrigin(); +OVRP_EXPORT ovrpPosef ovrp_GetNodeVelocity2(ovrpStep step, ovrpNode nodeId); +OVRP_EXPORT ovrpPosef ovrp_GetNodeAcceleration2(ovrpStep step, ovrpNode nodeId); +OVRP_EXPORT ovrpFrustumf ovrp_GetNodeFrustum(ovrpNode nodeId); +OVRP_EXPORT ovrpBool ovrp_GetAppMonoscopic(); +OVRP_EXPORT ovrpBool ovrp_SetAppMonoscopic(ovrpBool value); +OVRP_EXPORT ovrpBool ovrp_GetAppSRGB(); +OVRP_EXPORT ovrpBool ovrp_SetAppSRGB(ovrpBool value); +OVRP_EXPORT float ovrp_GetUserEyeDepth(); +OVRP_EXPORT ovrpBool ovrp_SetUserEyeDepth(float value); +OVRP_EXPORT ovrpBool ovrp_SetEyeTextureArrayEnabled(ovrpBool value); +OVRP_EXPORT ovrpBool ovrp_GetEyeTextureArrayEnabled(); +OVRP_EXPORT ovrpResult ovrp_GetAppAsymmetricFov(ovrpBool* useAsymmetricFov); +OVRP_EXPORT ovrpResult ovrp_SetAppAsymmetricFov(ovrpBool value); + + +OVRP_EXPORT ovrpBool ovrp_SetOverlayQuad3( + unsigned int flags, + void* textureLeft, + void* textureRight, + void* device, + ovrpPosef pose, + ovrpVector3f scale, + int layerIndex); + +OVRP_EXPORT ovrpResult ovrp_EnqueueSetupLayer(ovrpLayerDesc* desc, int* layerId); + +OVRP_EXPORT ovrpResult ovrp_EnqueueSetupLayer2(ovrpLayerDesc* desc, int compositionDepth, int* layerId); + +OVRP_EXPORT ovrpResult ovrp_EnqueueDestroyLayer(int* layerId); + +OVRP_EXPORT ovrpResult ovrp_GetLayerTexturePtr(int layerId, int stage, ovrpEye eyeId, void** texturePtr); + +OVRP_EXPORT ovrpResult ovrp_EnqueueSubmitLayer( + unsigned int flags, + void* textureLeft, + void* textureRight, + int layerId, + int frameIndex, + OVRP_CONSTREF(ovrpPosef) pose, + OVRP_CONSTREF(ovrpVector3f) scale, + int layerIndex); + +OVRP_EXPORT ovrpResult ovrp_EnqueueSubmitLayer2( + unsigned int flags, + void* textureLeft, + void* textureRight, + int layerId, + int frameIndex, + OVRP_CONSTREF(ovrpPosef) pose, + OVRP_CONSTREF(ovrpVector3f) scale, + int layerIndex, + ovrpBool overrideTextureRectMatrix, + OVRP_CONSTREF(ovrpTextureRectMatrixf) textureRectMatrix, + ovrpBool overridePerLayerColorScaleAndOffset, + OVRP_CONSTREF(ovrpVector4f) colorScale, + OVRP_CONSTREF(ovrpVector4f) colorOffset); + + + + + + + + + + + + + + + + + + + + + +// Previously deprecated +OVRP_EXPORT ovrpBool ovrp_Initialize(ovrpRenderAPIType apiType, void* platformArgs); +OVRP_EXPORT ovrpBoundaryGeometry ovrp_GetBoundaryGeometry(ovrpBoundaryType boundaryType); +OVRP_EXPORT void* ovrp_GetNativePointer(); +OVRP_EXPORT ovrpBool ovrp_DismissHSW(); +OVRP_EXPORT void* ovrp_GetAdapterId(); +OVRP_EXPORT int ovrp_GetBufferCount(); +OVRP_EXPORT ovrpBool ovrp_SetEyeTexture(ovrpEye eyeId, void* texture, void* device); + +OVRP_EXPORT ovrpBool ovrp_RecreateEyeTexture( + ovrpEye eyeId, + int stage, + void* device, + int height, + int width, + int samples, + ovrpBool isSRGB, + void* result); + +OVRP_EXPORT ovrpBool ovrp_ReleaseEyeTexture(ovrpEye eyeId, int stage); +OVRP_EXPORT ovrpPosef ovrp_GetEyePose(ovrpEye eyeId); +OVRP_EXPORT ovrpPosef ovrp_GetEyeVelocity(ovrpEye eyeId); +OVRP_EXPORT ovrpPosef ovrp_GetEyeAcceleration(ovrpEye eyeId); +OVRP_EXPORT ovrpFrustumf ovrp_GetEyeFrustum(ovrpEye eyeId); +OVRP_EXPORT ovrpPosef ovrp_GetTrackerPose(ovrpTracker trackerId); +OVRP_EXPORT ovrpFrustumf ovrp_GetTrackerFrustum(ovrpTracker trackerId); +OVRP_EXPORT ovrpBool ovrp_RecenterPose(); +OVRP_EXPORT ovrpInputState ovrp_GetInputState(ovrpController controllerMask); +OVRP_EXPORT ovrpBatteryStatus ovrp_GetBatteryStatus(); +OVRP_EXPORT ovrpBool ovrp_ShowUI(ovrpUI ui); +OVRP_EXPORT ovrpCaps ovrp_GetCaps(); +OVRP_EXPORT unsigned int ovrp_GetCaps2(unsigned int query); +OVRP_EXPORT ovrpBool ovrp_SetCaps(ovrpCaps caps); +OVRP_EXPORT ovrpStatus ovrp_GetStatus(); +OVRP_EXPORT unsigned int ovrp_GetStatus2(unsigned int query); +OVRP_EXPORT float ovrp_GetFloat(ovrpKey key); +OVRP_EXPORT ovrpBool ovrp_SetFloat(ovrpKey key, float value); +OVRP_EXPORT const char* ovrp_GetString(ovrpKey key); + +OVRP_EXPORT ovrpBool +ovrp_SetOverlayQuad(ovrpBool onTop, void* texture, void* device, ovrpPosef pose, ovrpVector3f scale); + +OVRP_EXPORT ovrpBool ovrp_SetOverlayQuad2( + ovrpBool onTop, + ovrpBool headLocked, + void* texture, + void* device, + ovrpPosef pose, + ovrpVector3f scale); + +OVRP_EXPORT ovrpResult ovrp_CalculateEyeLayerDesc( + ovrpLayout layout, + float textureScale, + int mipLevels, + int sampleCount, + ovrpTextureFormat format, + int layerFlags, + ovrpLayerDesc_EyeFov* layerDesc); + +OVRP_EXPORT ovrpBool ovrp_SetAppIgnoreVrFocus(ovrpBool value); +OVRP_EXPORT ovrpBool ovrp_GetHeadphonesPresent(); +OVRP_EXPORT ovrpBool ovrp_Update(int frameIndex); +OVRP_EXPORT ovrpPosef ovrp_GetNodePose(ovrpNode nodeId); +OVRP_EXPORT ovrpPosef ovrp_GetNodeVelocity(ovrpNode nodeId); +OVRP_EXPORT ovrpPosef ovrp_GetNodeAcceleration(ovrpNode nodeId); + +/// Gets the texture handle for a specific layer stage and eye. +OVRP_EXPORT ovrpResult ovrp_GetLayerTexture(int layerId, int stage, ovrpEye eyeId, ovrpTextureHandle* textureHandle); + +OVRP_EXPORT ovrpBool ovrp_SetupEyeTexture( + ovrpEye eyeId, + int stage, + void* device, + int height, + int width, + int samples, + ovrpBool isSRGB, + void* result); + +OVRP_EXPORT ovrpPosef ovrp_GetNodePose2(ovrpStep step, ovrpNode nodeId); + +OVRP_EXPORT ovrpResult ovrp_SetFunctionPointer(ovrpFunctionType funcType, void* funcPtr); + +// Return success if updating depth info is finished +OVRP_EXPORT ovrpResult ovrp_SetDepthCompositingInfo(float zNear, float zFar, ovrpBool isReverseZ); + +OVRP_EXPORT ovrpResult ovrp_SetOctilinearInfo(ovrpOctilinearLayout OctilinearLayout[ovrpEye_Count]); + +/// Gets the current pose, acceleration, and velocity of the given node on the given update cadence. +OVRP_EXPORT ovrpResult ovrp_GetNodePoseState2(ovrpStep step, ovrpNode nodeId, ovrpPoseStatef* nodePoseState); + +// Called by Unity render thread after finished each eye rendering +OVRP_EXPORT ovrpResult ovrp_EndEye2(ovrpEye eye, int frameIndex); + +// Update depth projection info, this is a replacement of ovrp_SetDepthCompositingInfo for more generic purpose +OVRP_EXPORT ovrpResult ovrp_SetDepthProjInfo(float zNear, float zFar, ovrpBool isReverseZ); + +// Deprecated +OVRP_EXPORT ovrpResult ovrp_SetASWEnable(ovrpBool enable); + +// Deprecated +OVRP_EXPORT ovrpResult ovrp_GetASWEnable(ovrpBool* enable); + +OVRP_EXPORT ovrpResult ovrp_GetSkeleton(ovrpSkeletonType skeletonType, ovrpSkeleton* skeleton); +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Insight.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Insight.h new file mode 100644 index 0000000000000000000000000000000000000000..d13d806edbaa226f0a1f44a907554fd381e75d0f --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Insight.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVR_Plugin_Insight_h +#define OVR_Plugin_Insight_h + +#include "OVR_Plugin_Types.h" + +#include <cstdint> + +#ifdef __cplusplus +extern "C" { +#endif + +/// Tests if Insight Passthrough is supported on the device +OVRP_EXPORT ovrpResult ovrp_IsInsightPassthroughSupported(ovrpBool* supported); + +/// Initialize Insight Passthrough functionality +OVRP_EXPORT ovrpResult ovrp_InitializeInsightPassthrough(); + +/// Shutdown Insight Passthrough functionality +OVRP_EXPORT ovrpResult ovrp_ShutdownInsightPassthrough(); + +/// Check whether Insight Passthrough functionality has been initialized +OVRP_EXPORT ovrpBool ovrp_GetInsightPassthroughInitialized(); + +/// Check whether Insight Passthrough functionality has been initialized +OVRP_EXPORT ovrpResult ovrp_GetInsightPassthroughInitializationState(); + +/// Create and initialize an Insight Passthrough triangle mesh +OVRP_EXPORT ovrpResult ovrp_CreateInsightTriangleMesh( + int layerId, + float* vertices, + int vertexCount, + int* triangles, + int triangleCount, + uint64_t* outMeshHandle); + +/// Destroy a triangle mesh +OVRP_EXPORT ovrpResult ovrp_DestroyInsightTriangleMesh(uint64_t meshHandle); + +/// Add a triangle mesh to the passthrough projection surface +OVRP_EXPORT ovrpResult ovrp_AddInsightPassthroughSurfaceGeometry( + int layerId, + uint64_t meshHandle, + ovrpMatrix4f transformation, + uint64_t* geometryInstanceHandle); + +/// Remove a geometry instance from the passthrough projection surface and destroy it. +OVRP_EXPORT ovrpResult ovrp_DestroyInsightPassthroughGeometryInstance(uint64_t geometryInstanceHandle); + +/// Update a transform of a geometry instance. +OVRP_EXPORT ovrpResult +ovrp_UpdateInsightPassthroughGeometryTransform(uint64_t geometryInstanceHandle, ovrpMatrix4f transformation); + +OVRP_EXPORT ovrpResult ovrp_SetInsightPassthroughStyle(int layerId, ovrpInsightPassthroughStyle style); + +// Set hands intensity +OVRP_EXPORT ovrpResult +ovrp_SetInsightPassthroughKeyboardHandsIntensity(int layerId, ovrpInsightPassthroughKeyboardHandsIntensity intensity); + +// Gets passthrough capabilities. +OVRP_EXPORT ovrpResult ovrp_GetPassthroughCapabilityFlags(ovrpInsightPassthroughCapabilityFlags* capabilities); + + + + + + + + + + + + + + + + + + + +#ifdef __cplusplus +} +#endif + +#endif // OVR_Plugin_Insight_h diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Ktx.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Ktx.h new file mode 100644 index 0000000000000000000000000000000000000000..ceaee7a746ec7978f24e918aaa842c048585817e --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Ktx.h @@ -0,0 +1,47 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVR_Plugin_KtxLoader_h +#define OVR_Plugin_KtxLoader_h + +#include "OVR_Plugin_Types.h" +#include <ktx.h> + +#ifdef __cplusplus +extern "C" { +#endif + +OVRP_EXPORT ovrpResult ovrp_KtxLoadFromMemory(unsigned char** data, unsigned int length, ktxTexture2** texture); +OVRP_EXPORT ovrpResult ovrp_KtxDestroy(ktxTexture2* texture); + +OVRP_EXPORT ovrpResult ovrp_KtxNeedsTranscoding(ktxTexture2* texture, bool* needsTranscoding); +OVRP_EXPORT ovrpResult ovrp_KtxTranscode(ktxTexture2* texture, unsigned int format); + +OVRP_EXPORT ovrpResult ovrp_KtxTextureWidth(ktxTexture2* texture, unsigned int* width); +OVRP_EXPORT ovrpResult ovrp_KtxTextureHeight(ktxTexture2* texture, unsigned int* height); +OVRP_EXPORT ovrpResult ovrp_KtxTextureSize(ktxTexture2* texture, unsigned int* size); + +OVRP_EXPORT ovrpResult ovrp_KtxGetTextureData(ktxTexture2* texture, unsigned char* data, unsigned int bufferSize); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Media.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Media.h new file mode 100644 index 0000000000000000000000000000000000000000..2f3acba793a6b51219a62de732929f5785c40d49 --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Media.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVR_Plugin_Media_h +#define OVR_Plugin_Media_h + +#include "OVR_Plugin_Types.h" + +#ifdef __cplusplus +extern "C" { +#endif + +OVRP_EXPORT ovrpResult ovrp_Media_Initialize(); +OVRP_EXPORT ovrpResult ovrp_Media_Shutdown(); +OVRP_EXPORT ovrpResult ovrp_Media_GetInitialized(ovrpBool* initialized); +OVRP_EXPORT ovrpResult ovrp_Media_Update(); + +OVRP_EXPORT ovrpResult ovrp_Media_GetMrcActivationMode(ovrpMediaMrcActivationMode* activationMode); +OVRP_EXPORT ovrpResult ovrp_Media_SetMrcActivationMode(ovrpMediaMrcActivationMode activationMode); +OVRP_EXPORT ovrpResult ovrp_Media_SetPlatformInitialized(); +OVRP_EXPORT ovrpResult ovrp_Media_GetPlatformCameraMode(ovrpPlatformCameraMode* platformCameraMode); +OVRP_EXPORT ovrpResult ovrp_Media_SetPlatformCameraMode(ovrpPlatformCameraMode platformCameraMode); +OVRP_EXPORT ovrpResult ovrp_Media_IsMrcEnabled(ovrpBool* mrcEnabled); +OVRP_EXPORT ovrpResult ovrp_Media_IsMrcActivated(ovrpBool* mrcActivated); +OVRP_EXPORT ovrpResult ovrp_Media_UseMrcDebugCamera(ovrpBool* useMrcDebugCamera); + +OVRP_EXPORT ovrpResult ovrp_Media_SetMrcInputVideoBufferType(ovrpMediaInputVideoBufferType inputVideoBufferType); +OVRP_EXPORT ovrpResult ovrp_Media_GetMrcInputVideoBufferType(ovrpMediaInputVideoBufferType* inputVideoBufferType); +OVRP_EXPORT ovrpResult ovrp_Media_SetMrcFrameSize(int frameWidth, int frameHeight); +OVRP_EXPORT ovrpResult ovrp_Media_GetMrcFrameSize(int* frameWidth, int* frameHeight); +OVRP_EXPORT ovrpResult ovrp_Media_SetMrcAudioSampleRate(int sampleRate); +OVRP_EXPORT ovrpResult ovrp_Media_GetMrcAudioSampleRate(int* sampleRate); +OVRP_EXPORT ovrpResult ovrp_Media_SetMrcFrameImageFlipped(ovrpBool flipped); +OVRP_EXPORT ovrpResult ovrp_Media_GetMrcFrameImageFlipped(ovrpBool* flipped); +OVRP_EXPORT ovrpResult ovrp_Media_SetMrcFrameInverseAlpha(ovrpBool inverseAlpha); +OVRP_EXPORT ovrpResult ovrp_Media_GetMrcFrameInverseAlpha(ovrpBool* inverseAlpha); +OVRP_EXPORT ovrpResult ovrp_Media_SetAvailableQueueIndexVulkan(unsigned int queueIndexVk); +OVRP_EXPORT ovrpResult ovrp_Media_EncodeMrcFrame( + void* videoData, + float* audioData, + int audioDataLen, + int audioChannels, + double timestamp, + int* outSyncId); +OVRP_EXPORT ovrpResult ovrp_Media_EncodeMrcFrameWithDualTextures( + void* backgroundTextureHandle, + void* foregroundTextureHandle, + float* audioData, + int audioDataLen, + int audioChannels, + double timestamp, + int* outSyncId); +OVRP_EXPORT ovrpResult ovrp_Media_SyncMrcFrame(int syncId); +OVRP_EXPORT ovrpResult ovrp_Media_EncodeMrcFrameWithPoseTime( + void* videoData, + float* audioData, + int audioDataLen, + int audioChannels, + double timestamp, + double poseTime, + int* outSyncId); +OVRP_EXPORT ovrpResult ovrp_Media_EncodeMrcFrameDualTexturesWithPoseTime( + void* backgroundTextureHandle, + void* foregroundTextureHandle, + float* audioData, + int audioDataLen, + int audioChannels, + double timestamp, + double poseTime, + int* outSyncId); +OVRP_EXPORT ovrpResult +ovrp_Media_SetHeadsetControllerPose(ovrpPosef headsetPose, ovrpPosef leftControllerPose, ovrpPosef rightControllerPose); +OVRP_EXPORT ovrpResult +ovrp_Media_EnumerateCameraAnchorHandles(int* inoutAnchorCount, ovrpCameraAnchorHandle* outHandleArray); +OVRP_EXPORT ovrpResult ovrp_Media_GetCurrentCameraAnchorHandle(ovrpCameraAnchorHandle* outHandle); +OVRP_EXPORT ovrpResult +ovrp_Media_GetCameraAnchorName(ovrpCameraAnchorHandle anchorHandle, char outAnchorName[OVRP_ANCHOR_NAME_SIZE]); +OVRP_EXPORT ovrpResult ovrp_Media_GetCameraAnchorHandle(const char* anchorName, ovrpCameraAnchorHandle* outHandle); +OVRP_EXPORT ovrpResult +ovrp_Media_GetCameraAnchorType(ovrpCameraAnchorHandle anchorHandle, ovrpCameraAnchorType* outAnchorType); +OVRP_EXPORT ovrpResult ovrp_Media_CreateCustomCameraAnchor(const char* anchorName, ovrpCameraAnchorHandle* outHandle); +OVRP_EXPORT ovrpResult ovrp_Media_DestroyCustomCameraAnchor(ovrpCameraAnchorHandle anchorHandle); +OVRP_EXPORT ovrpResult ovrp_Media_GetCustomCameraAnchorPose(ovrpCameraAnchorHandle anchorHandle, ovrpPosef* outPose); +OVRP_EXPORT ovrpResult ovrp_Media_SetCustomCameraAnchorPose(ovrpCameraAnchorHandle anchorHandle, ovrpPosef pose); +OVRP_EXPORT ovrpResult +ovrp_Media_GetCameraMinMaxDistance(ovrpCameraAnchorHandle anchorHandle, double* outMinDistance, double* outMaxDistance); +OVRP_EXPORT ovrpResult +ovrp_Media_SetCameraMinMaxDistance(ovrpCameraAnchorHandle anchorHandle, double minDistance, double maxDistance); +OVRP_EXPORT ovrpResult ovrp_Media_IsCastingToRemoteClient(ovrpBool* isCasting); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_MixedReality.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_MixedReality.h new file mode 100644 index 0000000000000000000000000000000000000000..28d92135ce0421b619f4638b17abcaa241bb4e9e --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_MixedReality.h @@ -0,0 +1,146 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVR_Plugin_MixedReality_h +#define OVR_Plugin_MixedReality_h + +#include "OVR_Plugin_Types.h" + +#if OVRP_MIXED_REALITY_PRIVATE +#include "OVR_Plugin_MixedReality_Private.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +//////////////////// Tracked Camera ////////////////////////// + +/// Initialize Mixed Reality functionalities +OVRP_EXPORT ovrpResult ovrp_InitializeMixedReality(); + +/// Shutdown Mixed Reality functionalities +OVRP_EXPORT ovrpResult ovrp_ShutdownMixedReality(); + +/// Check whether Mixed Reality functionalities has been initialized +OVRP_EXPORT ovrpBool ovrp_GetMixedRealityInitialized(); + +/// Update external camera. Need to be called before accessing the camera count or individual camera information +OVRP_EXPORT ovrpResult ovrp_UpdateExternalCamera(); + +/// Get the number of external cameras +OVRP_EXPORT ovrpResult ovrp_GetExternalCameraCount(int* cameraCount); + +/// Get the name of an external camera +OVRP_EXPORT ovrpResult ovrp_GetExternalCameraName(int cameraId, char cameraName[OVRP_EXTERNAL_CAMERA_NAME_SIZE]); + +/// Get intrinsics of an external camera +OVRP_EXPORT ovrpResult ovrp_GetExternalCameraIntrinsics(int cameraId, ovrpCameraIntrinsics* cameraIntrinsics); + +/// Get extrinsics of an external camera +OVRP_EXPORT ovrpResult ovrp_GetExternalCameraExtrinsics(int cameraId, ovrpCameraExtrinsics* cameraExtrinsics); + +/// Get the raw transform pose when the external camera was calibrated +OVRP_EXPORT ovrpResult ovrp_GetExternalCameraCalibrationRawPose(int cameraId, ovrpPosef* rawPose); + +/// Override the FOV of the external camera +OVRP_EXPORT ovrpResult ovrp_OverrideExternalCameraFov(int cameraId, ovrpBool useOverriddenFov, const ovrpFovf* fov); + +/// Get if the FOV of the external camera is overridden +OVRP_EXPORT ovrpResult ovrp_GetUseOverriddenExternalCameraFov(int cameraId, ovrpBool* useOverriddenFov); + +/// Override the Pose of the external camera. +OVRP_EXPORT ovrpResult +ovrp_OverrideExternalCameraStaticPose(int cameraId, ovrpBool useOverriddenPose, const ovrpPosef* pose); + +/// Get if the Pose of the external camera is overridden +OVRP_EXPORT ovrpResult ovrp_GetUseOverriddenExternalCameraStaticPose(int cameraId, ovrpBool* useOverriddenStaticPose); + +/// Helper function to get the camera pose in the tracking space +OVRP_EXPORT ovrpResult ovrp_GetExternalCameraPose(int cameraId, ovrpPosef* cameraPose); + +/// Helper function to get convert a pose in tracking space to camera space +OVRP_EXPORT ovrpResult +ovrp_ConvertPoseToCameraSpace(int cameraId, ovrpPosef* trackingSpacePose, ovrpPosef* cameraSpacePose); + +/// Reset the manual external camera +/// On Quest, it would stop listenting to the MRC port if needed +OVRP_EXPORT ovrpResult ovrp_ResetDefaultExternalCamera(); + +/// Set a manual external camera to the system. The manual external camera is valid when there is no camera +/// configuration can be loaded On Quest, it would start listenting to the MRC port if needed +OVRP_EXPORT ovrpResult ovrp_SetDefaultExternalCamera( + const char* cameraName, + const ovrpCameraIntrinsics* cameraIntrinsics, + const ovrpCameraExtrinsics* cameraExtrinsics); + +/// (PC only) set external camera intrinsics and extrinsics +OVRP_EXPORT ovrpResult ovrp_SetExternalCameraProperties( + const char* cameraName, + const ovrpCameraIntrinsics* cameraIntrinsics, + const ovrpCameraExtrinsics* cameraExtrinsics); + +// {{ DEPRECATED +// The following functions will be moved to OVR_Plugin_MixedReality_Deprecated.h after Unreal Plugin revision +OVRP_EXPORT ovrpResult +ovrp_EnumerateAllCameraDevices(ovrpCameraDevice* deviceArray, int deviceArraySize, int* deviceCount); +OVRP_EXPORT ovrpResult +ovrp_EnumerateAvailableCameraDevices(ovrpCameraDevice* deviceArray, int deviceArraySize, int* deviceCount); +OVRP_EXPORT ovrpResult ovrp_UpdateCameraDevices(); +OVRP_EXPORT ovrpResult ovrp_IsCameraDeviceAvailable2(ovrpCameraDevice camera, ovrpBool* available); +OVRP_EXPORT ovrpResult +ovrp_SetCameraDevicePreferredColorFrameSize(ovrpCameraDevice camera, ovrpSizei preferredColorFrameSize); +OVRP_EXPORT ovrpResult ovrp_OpenCameraDevice(ovrpCameraDevice camera); +OVRP_EXPORT ovrpResult ovrp_CloseCameraDevice(ovrpCameraDevice camera); +OVRP_EXPORT ovrpResult ovrp_HasCameraDeviceOpened2(ovrpCameraDevice camera, ovrpBool* opened); +OVRP_EXPORT ovrpResult ovrp_GetCameraDeviceIntrinsicsParameters( + ovrpCameraDevice camera, + ovrpBool* supportIntrinsics, + ovrpCameraDeviceIntrinsicsParameters* intrinsicsParameters); +OVRP_EXPORT ovrpResult ovrp_IsCameraDeviceColorFrameAvailable2(ovrpCameraDevice camera, ovrpBool* available); +OVRP_EXPORT ovrpResult ovrp_GetCameraDeviceColorFrameSize(ovrpCameraDevice camera, ovrpSizei* colorFrameSize); +OVRP_EXPORT ovrpResult ovrp_GetCameraDeviceColorFrameBgraPixels( + ovrpCameraDevice camera, + const ovrpByte** colorFrameBgraPixels, + int* colorFrameRowPitch); +OVRP_EXPORT ovrpResult ovrp_DoesCameraDeviceSupportDepth(ovrpCameraDevice camera, ovrpBool* supportDepth); +OVRP_EXPORT ovrpResult +ovrp_GetCameraDeviceDepthSensingMode(ovrpCameraDevice camera, ovrpCameraDeviceDepthSensingMode* depthSensingMode); +OVRP_EXPORT ovrpResult +ovrp_SetCameraDeviceDepthSensingMode(ovrpCameraDevice camera, ovrpCameraDeviceDepthSensingMode depthSensingMode); +OVRP_EXPORT ovrpResult +ovrp_GetCameraDevicePreferredDepthQuality(ovrpCameraDevice camera, ovrpCameraDeviceDepthQuality* depthQuality); +OVRP_EXPORT ovrpResult +ovrp_SetCameraDevicePreferredDepthQuality(ovrpCameraDevice camera, ovrpCameraDeviceDepthQuality depthQuality); +OVRP_EXPORT ovrpResult ovrp_IsCameraDeviceDepthFrameAvailable(ovrpCameraDevice camera, ovrpBool* available); +OVRP_EXPORT ovrpResult ovrp_GetCameraDeviceDepthFrameSize(ovrpCameraDevice camera, ovrpSizei* depthFrameSize); +OVRP_EXPORT ovrpResult +ovrp_GetCameraDeviceDepthFramePixels(ovrpCameraDevice camera, const float** depthFramePixels, int* depthFrameRowPitch); +OVRP_EXPORT ovrpResult ovrp_GetCameraDeviceDepthConfidencePixels( + ovrpCameraDevice camera, + const float** depthConfidencePixels, + int* depthConfidenceRowPitch); +// }} DEPRECATED + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_MixedReality_Deprecated.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_MixedReality_Deprecated.h new file mode 100644 index 0000000000000000000000000000000000000000..83c2b49e90f3aa2adf8d1103d6c897847f2bcdb5 --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_MixedReality_Deprecated.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVR_Plugin_MixedReality_Deprecated_h +#define OVR_Plugin_MixedReality_Deprecated_h + +#include "OVR_Plugin_MixedReality.h" + +#ifdef __cplusplus +extern "C" { +#endif + +OVRP_EXPORT ovrpBool ovrp_IsCameraDeviceAvailable(ovrpCameraDevice camera); +OVRP_EXPORT ovrpBool ovrp_HasCameraDeviceOpened(ovrpCameraDevice camera); +OVRP_EXPORT ovrpBool ovrp_IsCameraDeviceColorFrameAvailable(ovrpCameraDevice camera); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Types.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Types.h new file mode 100644 index 0000000000000000000000000000000000000000..635074cd361bdc0b8603d4882762cb554cd8ab98 --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Types.h @@ -0,0 +1,2933 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + + +#ifndef OVR_Plugin_Types_h +#define OVR_Plugin_Types_h + +#if !defined(OVRP_STRINGIFY) +#define OVRP_STRINGIFYIMPL(x) #x +#define OVRP_STRINGIFY(x) OVRP_STRINGIFYIMPL(x) +#endif + +#define OVRP_MAJOR_VERSION 1 +#define OVRP_MINOR_VERSION 81 +#define OVRP_PATCH_VERSION 0 + +#define OVRP_VERSION OVRP_MAJOR_VERSION, OVRP_MINOR_VERSION, OVRP_PATCH_VERSION +#define OVRP_VERSION_STR OVRP_STRINGIFY(OVRP_MAJOR_VERSION.OVRP_MINOR_VERSION.OVRP_PATCH_VERSION) + + + + +#define OVRP_VERSION_CHANNEL "Release" + + +#define OVRP_CURRENT_FRAMEINDEX -1 + +#ifndef OVRP_EXPORT +#ifdef _WIN32 +#define OVRP_EXPORT __declspec(dllexport) +#elif defined(__ANDROID__) +#define OVRP_EXPORT __attribute__((visibility("default"))) +#else +#define OVRP_EXPORT +#endif +#endif + +#if defined ANDROID || defined __linux__ +#define __cdecl +#endif + +#ifdef __cplusplus +#define OVRP_REF(Type) Type& +#define OVRP_CONSTREF(Type) const Type& +#define OVRP_DEFAULTVALUE(Value) = Value +#else +#define OVRP_REF(Type) Type* +#define OVRP_CONSTREF(Type) const Type* +#define OVRP_DEFAULTVALUE(Value) +#endif + +#define OVRP_UNUSED(x) ((void)(x)) + +#define OVRP_FILE_AND_LINE __FILE__ ":" OVRP_STRINGIFY(__LINE__) + +#ifndef OVRP_MIXED_REALITY_PRIVATE +#define OVRP_MIXED_REALITY_PRIVATE 0 +#endif + +#ifndef OVR_PLUGIN_PC_OPENXR +#define OVR_PLUGIN_PC_OPENXR 0 +#endif + +#ifndef OVR_PLUGIN_MOBILE_OPENXR +#define OVR_PLUGIN_MOBILE_OPENXR 0 +#endif + +#if OVR_PLUGIN_PC_OPENXR || OVR_PLUGIN_MOBILE_OPENXR +#define OVR_PLUGIN_USE_OPENXR 1 +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wnested-anon-types" +#pragma clang diagnostic ignored "-Wpedantic" +#endif // __clang__ + +/// True or false +enum { + ovrpBool_False = 0, + ovrpBool_True = 1, +}; +typedef int ovrpBool; + +/// Byte +typedef unsigned char ovrpByte; + +/// UInt32 +typedef unsigned int ovrpUInt32; + +/// Int16 +typedef short ovrpInt16; + +/// UInt16 +typedef unsigned short ovrpUInt16; + +/// Int64 +typedef long long ovrpInt64; + +/// UInt64 +typedef unsigned long long ovrpUInt64; + +/// Epsilon for floating point comparison +#define OVRP_FLOAT_EPSILON (1e-5f) + +/// Success and failure +typedef enum { + /// Success + ovrpSuccess = 0, + ovrpSuccess_EventUnavailable = 1, + ovrpSuccess_Pending = 2, + + /// Failure + ovrpFailure = -1000, + ovrpFailure_InvalidParameter = -1001, + ovrpFailure_NotInitialized = -1002, + ovrpFailure_InvalidOperation = -1003, + ovrpFailure_Unsupported = -1004, + ovrpFailure_NotYetImplemented = -1005, + ovrpFailure_OperationFailed = -1006, + ovrpFailure_InsufficientSize = -1007, + ovrpFailure_DataIsInvalid = -1008, + ovrpFailure_DeprecatedOperation = -1009, + ovrpFailure_ErrorLimitReached = -1010, + ovrpFailure_ErrorInitializationFailed = -1011, + + /// Space error cases + ovrpFailure_SpaceCloudStorageDisabled = -2000, + ovrpFailure_SpaceMappingInsufficient = -2001, + ovrpFailure_SpaceLocalizationFailed = -2002, + ovrpFailure_SpaceNetworkTimeout = -2003, + ovrpFailure_SpaceNetworkRequestFailed = -2004, +} ovrpResult; + +#define OVRP_SUCCESS(result) ((result) >= 0) +#define OVRP_FAILURE(result) ((result) < 0) + +/// XR API types +typedef enum { + ovrpXrApi_Unknown = 0, + ovrpXrApi_CAPI = 1, + ovrpXrApi_VRAPI = 2, + ovrpXrApi_OpenXR = 3, + ovrpXrApi_EnumSize = 0x7fffffff +} ovrpXrApi; + +/// Pre-initialization flags +typedef enum { + ovrpPreinitializeFlag_None = 0, + /// Unity native OpenXR Plugin is being used + ovrpPreinitializeFlag_UseUnityOpenXR = (1 << 0), + /// Unreal native OpenXR Plugin is being used + ovrpPreinitializeFlag_UseUnrealOpenXR = (1 << 1), + + + + + + ovrpPreinitializeFlag_EnumSize = 0x7fffffff +} ovrpPreinitializeFlags; + +/// Initialization flags +typedef enum { + /// Start GearVR battery and volume receivers + ovrpInitializeFlag_StartGearVRReceivers = (1 << 0), + /// Supports 2D/3D switching + ovrpInitializeFlag_SupportsVRToggle = (1 << 1), + /// Supports Life Cycle Focus (Dash) + ovrpInitializeFlag_FocusAware = (1 << 2), + /// DEPRECATED - Turn off Legacy Core Affinity Patch + /// Background: Some legacy unity versions set thread affinities wrong on newer hardware like Oculus Go + /// We need patch it in the runtime for published legacy apps. + /// This flag will be passed from fixed Unity versions explicitly, so we can skip the runtime patch mechanism since we + /// already have proper fixes. + /// Deprecated Background: Several Unity versions incorrectly indicated they handled applying thread affinity, so this + /// flag has been deprecated + /// in order to fallback to runtime thread affinity handling. In the future, a new flag will be introduced to allow + /// engine opt-out of + /// runtime affinity handling. + ovrpInitializeFlag_NoLegacyCoreAffinityPatch = (1 << 3), // DEPRECATED + + /// Allow to use sRGB frame buffer, we use it as an initialization flag because we need make the window surface + /// sRGB compilable, this can't be changed after window created. + ovrpInitializeFlag_SupportSRGBFrameBuffer = (1 << 4), + + + + + + + /// Enable Application SpaceWarp support + ovrpInitializeFlag_SupportAppSpaceWarp = (1 << 6), + + /// XR instance / session would be created by external engine, to support their OpenXR Plugins + ovrpInitializeFlag_ExternalXrObjects = (1 << 7), + + ovrpInitializeFlag_EnumSize = 0x7fffffff + +} ovrpInitializeFlags; + + +/// Thread Performance +typedef enum { + ovrpThreadPef_DeadLine_Normal = 0, + ovrpThreadPef_DeadLine_Hard = 1, + ovrpThreadPef_DeadLine_Soft = 2, + ovrpThreadPef_EnumSize = 0x7fffffff +} ovrpThreadPerf; + +/// Identifies an eye in a stereo pair. +typedef enum { + ovrpEye_Center = -2, + ovrpEye_None = -1, + ovrpEye_Left = 0, + ovrpEye_Right = 1, + ovrpEye_Count, + ovrpEye_EnumSize = 0x7fffffff +} ovrpEye; + +/// Identifies a hand. +typedef enum { + ovrpHand_None = -1, + ovrpHand_Left = 0, + ovrpHand_Right = 1, + ovrpHand_Count, + ovrpHand_EnumSize = 0x7fffffff +} ovrpHand; + +/// Identifies a tracked device object. +typedef enum { + ovrpDeviceObject_None = -1, + ovrpDeviceObject_Zero = 0, + ovrpDeviceObject_Count, + ovrpDeviceObject_EnumSize = 0x7fffffff +} ovrpDeviceObject; + +/// Identifies a tracking sensor. +typedef enum { + ovrpTracker_None = -1, + ovrpTracker_Zero = 0, + ovrpTracker_One = 1, + ovrpTracker_Two = 2, + ovrpTracker_Three = 3, + ovrpTracker_Count, + ovrpTracker_EnumSize = 0x7fffffff +} ovrpTracker; + +/// Identifies a tracked VR Node. +typedef enum { + ovrpNode_None = -1, + ovrpNode_EyeLeft = 0, + ovrpNode_EyeRight = 1, + ovrpNode_EyeCenter = 2, + ovrpNode_HandLeft = 3, + ovrpNode_HandRight = 4, + ovrpNode_TrackerZero = 5, + ovrpNode_TrackerOne = 6, + ovrpNode_TrackerTwo = 7, + ovrpNode_TrackerThree = 8, + ovrpNode_Head = 9, + ovrpNode_DeviceObjectZero = 10, + + + + ovrpNode_Count, + ovrpNode_EnumSize = 0x7fffffff +} ovrpNode; + +/// Identifies a tracking origin +typedef enum { + ovrpTrackingOrigin_EyeLevel = 0, + ovrpTrackingOrigin_FloorLevel = 1, + ovrpTrackingOrigin_Stage = 2, + + + + ovrpTrackingOrigin_View = 4, + ovrpTrackingOrigin_Count, + ovrpTrackingOrigin_EnumSize = 0x7fffffff +} ovrpTrackingOrigin; + +/// The charge status of a battery. +typedef enum { + ovrpBatteryStatus_Charging, + ovrpBatteryStatus_Discharging, + ovrpBatteryStatus_Full, + ovrpBatteryStatus_NotCharging, + ovrpBatteryStatus_Unknown, + ovrpBatteryStatus_EnumSize = 0x7fffffff +} ovrpBatteryStatus; + +// Handedness of user as specified in the mobile device +typedef enum { + ovrpHandedness_Unsupported = 0, + ovrpHandedness_LeftHanded = 1, + ovrpHandedness_RightHanded = 2 +} ovrpHandedness; + +/// An oculus platform UI. +typedef enum { + ovrpUI_None = -1, + ovrpUI_GlobalMenu = 0, + ovrpUI_ConfirmQuit, + ovrpUI_GlobalMenuTutorial, // Deprecated + ovrpUI_EnumSize = 0x7fffffff +} ovrpUI; + +/// A geographical region associated with the current system device. +typedef enum { + ovrpSystemRegion_Unspecified, + ovrpSystemRegion_Japan, + ovrpSystemRegion_China, + ovrpSystemRegion_EnumSize = 0x7fffffff +} ovrpSystemRegion; + +typedef enum { + ovrpSystemHeadset_None = 0, + + // Mobile & Standalone headsets + ovrpSystemHeadset_GearVR_R320, // Note4 Innovator + ovrpSystemHeadset_GearVR_R321, // S6 Innovator + ovrpSystemHeadset_GearVR_R322, // GearVR Commercial 1 + ovrpSystemHeadset_GearVR_R323, // GearVR Commercial 2 (USB Type C) + ovrpSystemHeadset_GearVR_R324, // GearVR Commercial 3 (USB Type C) + ovrpSystemHeadset_GearVR_R325, // GearVR Commercial 4 (USB Type C) + ovrpSystemHeadset_Oculus_Go, // Oculus Go Commercial 1 + ovrpSystemHeadset_Oculus_Quest, // Oculus Quest + ovrpSystemHeadset_Oculus_Quest_2, // Oculus Quest 2 + ovrpSystemHeadset_Meta_Quest_Pro, // Meta Quest Pro + ovrpSystemHeadset_Placeholder_11, + ovrpSystemHeadset_Placeholder_12, + ovrpSystemHeadset_Placeholder_13, + ovrpSystemHeadset_Placeholder_14, + + // PC headsets + ovrpSystemHeadset_Rift_DK1 = 0x1000, + ovrpSystemHeadset_Rift_DK2, + ovrpSystemHeadset_Rift_CV1, + ovrpSystemHeadset_Rift_CB, + ovrpSystemHeadset_Rift_S, + ovrpSystemHeadset_Oculus_Link_Quest, // Quest connected through Oculus Link + ovrpSystemHeadset_Oculus_Link_Quest_2, + ovrpSystemHeadset_Meta_Link_Quest_Pro, + ovrpSystemHeadset_PC_Placeholder_4104, + ovrpSystemHeadset_PC_Placeholder_4105, + ovrpSystemHeadset_PC_Placeholder_4106, + ovrpSystemHeadset_PC_Placeholder_4107, + ovrpSystemHeadset_EnumSize = 0x7fffffff +} ovrpSystemHeadset; + +/// These types are used to hide platform-specific details when passing +/// render device, OS, and texture data to the API. +/// +/// The benefit of having these wrappers versus platform-specific API functions is +/// that they allow game glue code to be portable. A typical example is an +/// engine that has multiple back ends, say GL and D3D. Portable code that calls +/// these back ends may also use LibOVR. To do this, back ends can be modified +/// to return portable types such as ovrTexture and ovrRenderAPIConfig. +typedef enum { + ovrpRenderAPI_None, + ovrpRenderAPI_OpenGL, + ovrpRenderAPI_Android_GLES, // Deprecated, use ovrpRenderAPI_OpenGL instead + ovrpRenderAPI_D3D9, // Deprecated, unsupported + ovrpRenderAPI_D3D10, // Deprecated, unsupported + ovrpRenderAPI_D3D11, + ovrpRenderAPI_D3D12, + ovrpRenderAPI_Vulkan, + ovrpRenderAPI_Count, + ovrpRenderAPI_EnumSize = 0x7fffffff +} ovrpRenderAPIType; + +/// Identifies a controller button. +typedef enum { + ovrpButton_None = 0, + ovrpButton_A = 0x00000001, + ovrpButton_B = 0x00000002, + ovrpButton_X = 0x00000100, + ovrpButton_Y = 0x00000200, + ovrpButton_Up = 0x00010000, + ovrpButton_Down = 0x00020000, + ovrpButton_Left = 0x00040000, + ovrpButton_Right = 0x00080000, + ovrpButton_Start = 0x00100000, + ovrpButton_Back = 0x00200000, + ovrpButton_LShoulder = 0x00000800, + ovrpButton_LThumb = 0x00000400, + ovrpButton_LTouchpad = 0x40000000, + ovrpButton_RShoulder = 0x00000008, + ovrpButton_RThumb = 0x00000004, + ovrpButton_RTouchpad = 0x80000000, + ovrpButton_VolUp = 0x00400000, + ovrpButton_VolDown = 0x00800000, + ovrpButton_Home = 0x01000000, + ovrpButton_EnumSize = 0x7fffffff +} ovrpButton; + +/// Identifies a controller touch. +typedef enum { + ovrpTouch_None = 0, + ovrpTouch_A = ovrpButton_A, + ovrpTouch_B = ovrpButton_B, + ovrpTouch_X = ovrpButton_X, + ovrpTouch_Y = ovrpButton_Y, + ovrpTouch_LIndexTrigger = 0x00001000, + ovrpTouch_LThumb = ovrpButton_LThumb, + ovrpTouch_LThumbRest = 0x00000800, + ovrpTouch_LTouchpad = ovrpButton_LTouchpad, + ovrpTouch_RIndexTrigger = 0x00000010, + ovrpTouch_RThumb = ovrpButton_RThumb, + ovrpTouch_RThumbRest = 0x00000008, + ovrpTouch_RTouchpad = ovrpButton_RTouchpad, + ovrpTouch_EnumSize = 0x7fffffff +} ovrpTouch; + +/// Identifies a controller near touch. +typedef enum { + ovrpNearTouch_None = 0, + ovrpNearTouch_LIndexTrigger = 0x00000001, + ovrpNearTouch_LThumbButtons = 0x00000002, + ovrpNearTouch_RIndexTrigger = 0x00000004, + ovrpNearTouch_RThumbButtons = 0x00000008, + ovrpNearTouch_EnumSize = 0x7fffffff +} ovrpNearTouch; + +/// Identifies a controller. +typedef enum { + ovrpController_None = 0, + ovrpController_LTouch = 0x01, + ovrpController_RTouch = 0x02, + ovrpController_Touch = ovrpController_LTouch | ovrpController_RTouch, + ovrpController_Remote = 0x04, + ovrpController_Gamepad = 0x10, + ovrpController_LHand = 0x20, + ovrpController_RHand = 0x40, + ovrpController_Hands = ovrpController_LHand | ovrpController_RHand, + ovrpController_Touchpad_DEPRECATED = 0x08000000, + ovrpController_LTrackedRemote = 0x01000000, + ovrpController_RTrackedRemote = 0x02000000, + ovrpController_Active = 0x80000000, + ovrpController_EnumSize = 0x7fffffff +} ovrpController; + + +/// Identifies a haptics location on a controller. +typedef enum { + ovrpHapticsLocation_None = 0, + ovrpHapticsLocation_Hand = 0x01, + ovrpHapticsLocation_Thumb = 0x02, + ovrpHapticsLocation_Index = 0x04, + ovrpHapticsLocation_EnumSize = 0x7fffffff +} ovrpHapticsLocation; + +/// Used to specify recentering behavior. +typedef enum { + /// Recenter all default axes as defined by the current tracking origin type. + ovrpRecenterFlag_Default = 0x00000000, + /// Recenter only controllers that require drift correction. + ovrpRecenterFlag_Controllers = 0x40000000, + /// Clear the ShouldRecenter flag and leave all axes unchanged. Useful for apps that perform + /// custom recentering logic. + ovrpRecenterFlag_IgnoreAll = 0x80000000, + ovrpRecenterFlag_EnumSize = 0x7fffffff +} ovrpRecenterFlag; + +/// Logging levels +typedef enum { + ovrpLogLevel_Debug = 0, + ovrpLogLevel_Info = 1, + ovrpLogLevel_Error = 2, + ovrpLogLevel_EnumSize = 0x7fffffff +} ovrpLogLevel; + +/// Foveation levels +/// +/// Levels should be consecutive integer enums, otherwise change GetTiledMultiResLevel +/// and SetTiledMultiResLevel to work without that assumption +typedef enum { + ovrpTiledMultiResLevel_Off = 0, + ovrpTiledMultiResLevel_LMSLow = 1, + ovrpTiledMultiResLevel_LMSMedium = 2, + ovrpTiledMultiResLevel_LMSHigh = 3, + ovrpTiledMultiResLevel_LMSHighTop = 4, + ovrpTiledMultiResLevel_EnumSize = 0x7fffffff +} ovrpTiledMultiResLevel; + +typedef enum { + ovrpFoveationFlag_EyeTracked = (1 << 0), + + + +} ovrpFoveationFlags; + +/// Control the activation of mixed reality capture +typedef enum { + ovrpMediaMrcActivationMode_Automatic = 0, + ovrpMediaMrcActivationMode_Disabled = 1, + ovrpMediaMrcActivationMode_EnumSize = 0x7fffffff +} ovrpMediaMrcActivationMode; + +/// Control the platform camera status +typedef enum { + ovrpPlatformCameraMode_Disabled = -1, + ovrpPlatformCameraMode_Initialized = 0, + ovrpPlatformCameraMode_UserControlled = 1, // Ex: Quest user grab and + ovrpPlatformCameraMode_SmartNavigated = 2, // Ex: Program to follow/zoom, avoid obstacle ...etc + ovrpPlatformCameraMode_StabilizedPoV = 3, // Ex: Stabilized 1st person view + ovrpPlatformCameraMode_RemoteDroneControlled = 4, // Ex: Control by remote clients + ovrpPlatformCameraMode_RemoteSpatialMapped = 5, // Ex: Control by anchor pose and SLAM tracking + ovrpPlatformCameraMode_EnumSize = 0x7fffffff +} ovrpPlatformCameraMode; + +/// Media encoder input buffer types +typedef enum { + /// raw memory. pixel format in RGBA + ovrpMediaInputVideoBufferType_Memory = 0, + /// texture handle (e.g. texId if GLES) + ovrpMediaInputVideoBufferType_TextureHandle = 1, + ovrpMediaInputVideoBufferType_EnumSize = 0x7fffffff +} ovrpMediaInputVideoBufferType; + +/// CPU/GPU Performance Levels +typedef enum { + ovrpProcessorPerformanceLevel_PowerSavings = 0, + ovrpProcessorPerformanceLevel_SustainedLow = 1, + ovrpProcessorPerformanceLevel_SustainedHigh = 2, + ovrpProcessorPerformanceLevel_Boost = 3, + ovrpProcessorPerformanceLevel_EnumSize = 0x7fffffff +} ovrpProcessorPerformanceLevel; + + + + + + + + + + + + + + + + +/// Feature fidelity for XR Runtime Performance Manager +typedef enum { + ovrpFeatureType_HandTracking = 0, + ovrpFeatureType_KeyboardTracking = 1, + ovrpFeatureType_EyeTracking = 2, + ovrpFeatureType_FaceTracking = 3, + ovrpFeatureType_BodyTracking = 4, + ovrpFeatureType_Passthrough = 5, + ovrpFeatureType_GazeBasedFoveatedRendering = 6, + ovrpFeatureType_Count, + ovrpFeatureType_EnumSize = 0x7fffffff +} ovrpFeatureType; + +typedef enum { + ovrpFeatureFidelity_Default = -1, + ovrpFeatureFidelity_Low = 0, + ovrpFeatureFidelity_MediumLow = 1, + ovrpFeatureFidelity_Medium = 2, + ovrpFeatureFidelity_MediumHigh = 3, + ovrpFeatureFidelity_High = 4, + ovrpFeatureFidelity_EnumSize = 0x7fffffff +} ovrpFeatureFidelity; + +typedef enum { + ovrpFeatureEnableState_Default = -1, + ovrpFeatureEnableState_Off = 0, + ovrpFeatureEnableState_On = 1, + ovrpFeatureEnableState_EnumSize = 0x7fffffff +} ovrpFeatureEnableState; + +typedef struct { + ovrpFeatureEnableState enableState; + ovrpFeatureFidelity fidelity; +} ovrpFeatureState; + +#if defined(__arm__) +typedef void (*ovrpLogCallback)(ovrpLogLevel, const char*); +typedef void (*ovrpLogCallback2)(ovrpLogLevel, const char* /*msg*/, int /*length*/); +#else +typedef void(__cdecl* ovrpLogCallback)(ovrpLogLevel, const char*); +typedef void(__cdecl* ovrpLogCallback2)(ovrpLogLevel, const char* /*msg*/, int /*length*/); +#endif + +typedef struct { + int MajorVersion; + int MinorVersion; + int PatchVersion; +} ovrpVersion; + +typedef struct { + float LatencyRender; + float LatencyTimewarp; + float LatencyPostPresent; + float ErrorRender; + float ErrorTimewarp; +} ovrpAppLatencyTimings; + +enum { ovrpAppPerfFrameStatsMaxCount = 5 }; + +/// App Perf Frame Stats +typedef struct { + int HmdVsyncIndex; + + int AppFrameIndex; + int AppDroppedFrameCount; + float AppMotionToPhotonLatency; + float AppQueueAheadTime; + float AppCpuElapsedTime; + float AppGpuElapsedTime; + + int CompositorFrameIndex; + int CompositorDroppedFrameCount; + float CompositorLatency; + float CompositorCpuElapsedTime; + float CompositorGpuElapsedTime; + float CompositorCpuStartToGpuEndElapsedTime; + float CompositorGpuEndToVsyncElapsedTime; +} ovrpAppPerfFrameStats; + +/// App Perf Stats +typedef struct { + ovrpAppPerfFrameStats FrameStats[ovrpAppPerfFrameStatsMaxCount]; + int FrameStatsCount; + ovrpBool AnyFrameStatsDropped; + float AdaptiveGpuPerformanceScale; +} ovrpAppPerfStats; + +/// Cross-platform perf metrics +typedef enum { + ovrpPerfMetrics_App_CpuTime_Float = 0, + ovrpPerfMetrics_App_GpuTime_Float = 1, + ovrpPerfMetrics_App_MotionToPhotonLatencyTime_Float_DEPRECATED = 2, + + ovrpPerfMetrics_Compositor_CpuTime_Float = 3, + ovrpPerfMetrics_Compositor_GpuTime_Float = 4, + ovrpPerfMetrics_Compositor_DroppedFrameCount_Int = 5, + ovrpPerfMetrics_Compositor_LatencyTime_Float_DEPRECATED = 6, + + ovrpPerfMetrics_System_GpuUtilPercentage_Float = 7, + ovrpPerfMetrics_System_CpuUtilAveragePercentage_Float = 8, + ovrpPerfMetrics_System_CpuUtilWorstPercentage_Float = 9, + + // Added 1.32.0 + ovrpPerfMetrics_Device_CpuClockFrequencyInMHz_Float = 10, // Deprecated 1.68.0 + ovrpPerfMetrics_Device_GpuClockFrequencyInMHz_Float = 11, // Deprecated 1.68.0 + ovrpPerfMetrics_Device_CpuClockLevel_Int = 12, // Deprecated 1.68.0 + ovrpPerfMetrics_Device_GpuClockLevel_Int = 13, // Deprecated 1.68.0 + + ovrpPerfMetrics_Compositor_SpaceWarp_Mode_Int = 14, + + ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float = 32, + ovrpPerfMetrics_Device_CpuCore1UtilPercentage_Float = ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float + 1, + ovrpPerfMetrics_Device_CpuCore2UtilPercentage_Float = ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float + 2, + ovrpPerfMetrics_Device_CpuCore3UtilPercentage_Float = ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float + 3, + ovrpPerfMetrics_Device_CpuCore4UtilPercentage_Float = ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float + 4, + ovrpPerfMetrics_Device_CpuCore5UtilPercentage_Float = ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float + 5, + ovrpPerfMetrics_Device_CpuCore6UtilPercentage_Float = ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float + 6, + ovrpPerfMetrics_Device_CpuCore7UtilPercentage_Float = ovrpPerfMetrics_Device_CpuCore0UtilPercentage_Float + 7, + // Enum value 32~63 are reserved for CPU Cores' utilization (assuming at most 32 cores). + + ovrpPerfMetrics_Count, + ovrpPerfMetrics_Max = 0x7fffffff, +} ovrpPerfMetrics; + +/// A 2D size with integer components. +typedef struct { + int w, h; +} ovrpSizei; + +/// A 2D size with float components. +typedef struct { + float w, h; +} ovrpSizef; + +/// A 3D size (width, height, depth) with float components. +typedef struct { + float w, h, d; +} ovrpSize3f; + +/// A 2D vector with integer components. +typedef struct { + int x, y; +} ovrpVector2i; + +/// A 2D vector with float components. +typedef struct { + float x, y; +} ovrpVector2f; + +/// A 3D vector with float components. +typedef struct { + float x, y, z; +} ovrpVector3f; + +/// A 4D vector with float components. +typedef struct { + float x, y, z, w; +} ovrpVector4f; + +/// A 4D vector with Int16 components. +typedef struct { + ovrpInt16 x, y, z, w; +} ovrpVector4s; + +/// A quaternion rotation. +typedef struct { + float x, y, z, w; +} ovrpQuatf; + +/// Row-major 4x4 matrix. +typedef struct { + float M[4][4]; +} ovrpMatrix4f; + +/// Position and orientation together. +typedef struct { + ovrpQuatf Orientation; + ovrpVector3f Position; +} ovrpPosef; + +/// Equivalent to XrSpaceLocationFlags, see openxr.h for flag values +typedef ovrpUInt64 ovrpSpaceLocationFlags; + +typedef struct { + ovrpSpaceLocationFlags locationFlags; + ovrpPosef pose; +} ovrpSpaceLocationf; + +/// Position and orientation together. +typedef struct { + ovrpPosef Pose; + ovrpVector3f Velocity; + ovrpVector3f Acceleration; + ovrpVector3f AngularVelocity; + ovrpVector3f AngularAcceleration; + double Time; +} ovrpPoseStatef; + +/// Asymmetric fov port +typedef struct { + float UpTan; + float DownTan; + float LeftTan; + float RightTan; +} ovrpFovf; + +/// Asymmetric frustum for a camera. +typedef struct { + /// Near clip plane. + float zNear; + /// Far clip plane. + float zFar; + ovrpFovf Fov; +} ovrpFrustum2f; + +/// A 2D rectangle with a position and size as integers. +typedef struct { + ovrpVector2i Pos; + ovrpSizei Size; +} ovrpRecti; + +/// A 2D rectangle with a position and size as floats. +typedef struct { + ovrpVector2f Pos; + ovrpSizef Size; +} ovrpRectf; + +/// A 3D bounds with a position and size as floats. +/// \note: Bounds is defined in Unity with center & extent (half size) but ovrpBoundsf here is +/// defined consistent with ovrpRectf using Pos (min) & Size. +typedef struct { + ovrpVector3f Pos; + ovrpSize3f Size; +} ovrpBoundsf; + +typedef struct { + ovrpRectf LeftRect; + ovrpRectf RightRect; + ovrpVector4f LeftScaleBias; + ovrpVector4f RightScaleBias; +} ovrpTextureRectMatrixf; + +typedef struct { + float WarpLeft; + float WarpRight; + float WarpUp; + float WarpDown; + float SizeLeft; + float SizeRight; + float SizeUp; + float SizeDown; +} ovrpOctilinearLayout; + +typedef struct { + float r, g, b, a; +} ovrpColorf; + +/// Describes Input State for use with Gamepads and Oculus Controllers. +typedef struct { + unsigned int ConnectedControllerTypes; + unsigned int Buttons; + unsigned int Touches; + unsigned int NearTouches; + float IndexTrigger[2]; + float HandTrigger[2]; + ovrpVector2f Thumbstick[2]; + ovrpVector2f Touchpad[2]; + unsigned char BatteryPercentRemaining[2]; + unsigned char RecenterCount[2]; + unsigned char Reserved[28]; +} ovrpControllerState4; + +typedef struct { + unsigned int ConnectedControllerTypes; + unsigned int Buttons; + unsigned int Touches; + unsigned int NearTouches; + float IndexTrigger[2]; + float HandTrigger[2]; + ovrpVector2f Thumbstick[2]; + ovrpVector2f Touchpad[2]; + unsigned char BatteryPercentRemaining[2]; + unsigned char RecenterCount[2]; + float ThumbRestForce[2]; + float StylusForce[2]; + float IndexTriggerCurl[2]; + float IndexTriggerSlide[2]; +} ovrpControllerState5; + + + + + + + + + + +/// Describes Haptics Buffer for use with Oculus Controllers. +typedef struct { + const void* Samples; + int SamplesCount; +} ovrpHapticsBuffer; + +typedef struct { + int SamplesAvailable; + int SamplesQueued; +} ovrpHapticsState; + +typedef struct { + int SampleRateHz; + int SampleSizeInBytes; + int MinimumSafeSamplesQueued; + int MinimumBufferSamplesCount; + int OptimalBufferSamplesCount; + int MaximumBufferSamplesCount; +} ovrpHapticsDesc; + +typedef struct { + float Duration; + ovrpUInt32 AmplitudeCount; + const float* Amplitudes; +} ovrpHapticsAmplitudeEnvelopeVibration; + +typedef struct { + ovrpUInt32 BufferSize; + const float* Buffer; + float SampleRateHz; + ovrpBool Append; + ovrpUInt32* SamplesConsumed; +} ovrpHapticsPcmVibration; + +typedef enum ovrpHapticsConstants_ { + ovrpHapticsConstants_MaxSamples = 4000, + ovrpHapticsConstants_EnumSize = 0x7fffffff +} ovrpHapticsConstants; + +/// Boundary types that specify a surface in the boundary system +typedef enum { + /// Outer boundary - closely represents user setup walls, floor and ceiling + ovrpBoundary_Outer = 0x0001, + /// Play area - smaller convex area inside outer boundary where gameplay happens + ovrpBoundary_PlayArea = 0x0100, +} ovrpBoundaryType; + +/// Contains boundary test information +typedef struct { + /// Indicates if the boundary system is being triggered and visible + ovrpBool IsTriggering; + /// Distance to the closest play area or outer boundary surface + float ClosestDistance; + /// Closest point in the surface + ovrpVector3f ClosestPoint; + /// Normal of the closest point + ovrpVector3f ClosestPointNormal; +} ovrpBoundaryTestResult; + +/// Boundary system look and feel +typedef struct { + // Modulate color and alpha (color, brightness and opacity) + ovrpColorf Color; +} ovrpBoundaryLookAndFeel; + +/// Boundary system geometry +typedef struct { + /// The boundary type that the geometry represents. + ovrpBoundaryType BoundaryType; + /// A pointer to a clock-wise ordered array of points. Max count of 256. + ovrpVector3f Points[256]; + /// The number of points. Max count of 256. + int PointsCount; +} ovrpBoundaryGeometry; + +typedef struct { + /// Distance between eyes. + float InterpupillaryDistance; + /// Eye height relative to the ground. + float EyeHeight; + /// Eye offset forward from the head center at EyeHeight. + float HeadModelDepth; + /// Neck joint offset down from the head center at EyeHeight. + float HeadModelHeight; +} ovrpHeadModelParms; + +typedef enum { ovrpFunctionEndFrame = 0, ovrpFunctionCreateTexture } ovrpFunctionType; + +/// Camera status +typedef enum { + ovrpCameraStatus_None, + ovrpCameraStatus_Connected, + ovrpCameraStatus_Calibrating, + ovrpCameraStatus_CalibrationFailed, + ovrpCameraStatus_Calibrated, + ovrpCameraStatus_ThirdPerson, + ovrpCameraStatus_EnumSize = 0x7fffffff +} ovrpCameraStatus; + +// Camera anchor types +typedef enum { + ovrpCameraAnchorType_PreDefined = 0, + ovrpCameraAnchorType_Custom = 1, + ovrpCameraAnchorType_Count, + ovrpCameraAnchorType_EnumSize = 0x7fffffff +} ovrpCameraAnchorType; + +/// Camera intrinsics +typedef struct { + ovrpBool IsValid; + double LastChangedTimeSeconds; + ovrpFovf FOVPort; + float VirtualNearPlaneDistanceMeters; + float VirtualFarPlaneDistanceMeters; + ovrpSizei ImageSensorPixelResolution; +} ovrpCameraIntrinsics; + +/// Camera extrinsics +typedef struct { + ovrpBool IsValid; + double LastChangedTimeSeconds; + ovrpCameraStatus CameraStatus; + ovrpNode AttachedToNode; + ovrpPosef RelativePose; +} ovrpCameraExtrinsics; + +typedef void* ovrpCameraAnchorHandle; +#define OVRP_ANCHOR_INVALID_HANDLE nullptr + +#define OVRP_EXTERNAL_CAMERA_NAME_SIZE 32 +#define OVRP_ANCHOR_NAME_SIZE 32 + +#if !OVRP_MIXED_REALITY_PRIVATE +/// Unified camera device types +typedef enum { + ovrpCameraDevice_None = 0, + ovrpCameraDevice_WebCamera_First = 100, + ovrpCameraDevice_WebCamera0 = ovrpCameraDevice_WebCamera_First + 0, + ovrpCameraDevice_WebCamera1 = ovrpCameraDevice_WebCamera_First + 1, + ovrpCameraDevice_WebCamera_Last = ovrpCameraDevice_WebCamera1, + ovrpCameraDevice_DEPRECATED_DEVICE = 300, // ovrpCameraDevice_ZEDStereoCamera before deprecation + ovrpCameraDevice_EnumSize = 0x7fffffff +} ovrpCameraDevice; +#endif + +typedef enum { + ovrpCameraDeviceDepthSensingMode_Standard = 0, + ovrpCameraDeviceDepthSensingMode_Fill, + ovrpCameraDeviceDepthSensingMode_EnumSize = 0x7fffffff +} ovrpCameraDeviceDepthSensingMode; + +typedef enum { + ovrpCameraDeviceDepthQuality_Low = 0, + ovrpCameraDeviceDepthQuality_Medium, + ovrpCameraDeviceDepthQuality_High, + ovrpCameraDeviceDepthQuality_EnumSize = 0x7fffffff +} ovrpCameraDeviceDepthQuality; + +typedef struct { + float fx; /* Focal length in pixels along x axis. */ + float fy; /* Focal length in pixels along y axis. */ + float cx; /* Optical center along x axis, defined in pixels (usually close to width/2). */ + float cy; /* Optical center along y axis, defined in pixels (usually close to height/2). */ + double disto[5]; /* Distortion factor : [ k1, k2, p1, p2, k3 ]. Radial (k1,k2,k3) and Tangential (p1,p2) distortion.*/ + float v_fov; /* Vertical field of view after stereo rectification, in degrees. */ + float h_fov; /* Horizontal field of view after stereo rectification, in degrees.*/ + float d_fov; /* Diagonal field of view after stereo rectification, in degrees.*/ + int w; /* Resolution width */ + int h; /* Resolution height */ +} ovrpCameraDeviceIntrinsicsParameters; + +const static ovrpPosef s_identityPose = {{0, 0, 0, 1}, {0, 0, 0}}; +const static ovrpPoseStatef s_identityPoseState = + {{{0, 0, 0, 1}, {0, 0, 0}}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, {0, 0, 0}, 0}; +const static ovrpFrustum2f s_identityFrustum2 = {0, 0, {0, 0, 0, 0}}; +const static ovrpVector4f s_vec4Zero = {0, 0, 0, 0}; +const static ovrpVector3f s_vec3Zero = {0, 0, 0}; +const static ovrpVector2f s_vec2Zero = {0, 0}; +const static ovrpVector3f s_vec3One = {1, 1, 1}; +const static ovrpQuatf s_identityQuat = {0, 0, 0, 1}; +const static ovrpFovf s_identityFov = {1.0f, 1.0f, 1.0f, 1.0f}; +const static ovrpCameraIntrinsics s_invalidCameraIntrinsics = {ovrpBool_False, -1, {0, 0, 0, 0}, 0, 0, {0, 0}}; +const static ovrpCameraExtrinsics s_invalidCameraExtrinsics = + {ovrpBool_False, -1, ovrpCameraStatus_None, ovrpNode_None, {{0, 0, 0, 1}, {0, 0, 0}}}; + +/// Texture handle which can be cast to GLuint, VkImage, ID3D11Texture2D*, or ID3D12Resource* +typedef unsigned long long ovrpTextureHandle; + +/// Flags passed to ovrp_SetupDistortionWindow. +typedef enum { + ovrpDistortionWindowFlag_None = 0x00000000, + /// If true, the distortion window and eye buffers are set up to handle DRM-protected content. + ovrpDistortionWindowFlag_Protected = 0x00000001, + /// If true, the compositor's graphics device skips error checking to improve performance. + ovrpDistortionWindowFlag_NoErrorContext = 0x00000002, + /// Reserved 0x00000004 in ovrp_SetupDistortionWindow3 + + ovrpDistortionWindowFlag_PhaseSync = 0x00000008, + + ovrpDistortionWindowFlag_EnumSize = 0x7fffffff +} ovrpDistortionWindowFlag; + +/// A timestep type corresponding to a use case for tracking data. +typedef enum { + /// Updated from game thread at end of frame, to hand-off state to Render thread. + ovrpStep_Render = -1, + /// Updated from physics thread, once per simulation step. + ovrpStep_Physics = 0, + ovrpStep_EnumSize = 0x7fffffff +} ovrpStep; + +typedef enum { + ovrpShape_Quad = 0, + ovrpShape_Cylinder = 1, + ovrpShape_Cubemap = 2, + ovrpShape_EyeFov = 3, + ovrpShape_OffcenterCubemap = 4, + ovrpShape_Equirect = 5, + ovrpShape_ReconstructionPassthrough = 7, + ovrpShape_SurfaceProjectedPassthrough = 8, + ovrpShape_Fisheye = 9, + ovrpShape_KeyboardHandsPassthrough = 10, + ovrpShape_KeyboardMaskedHandsPassthrough = 11, + ovrpShape_EnumSize = 0xF +} ovrpShape; + +typedef enum { + ovrpLayout_Stereo = 0, + ovrpLayout_Mono = 1, + ovrpLayout_DoubleWide = 2, + ovrpLayout_Array = 3, + ovrpLayout_EnumSize = 0xF +} ovrpLayout; + +/// A texture format. +typedef enum { + ovrpTextureFormat_R8G8B8A8_sRGB = 0, + ovrpTextureFormat_R8G8B8A8 = 1, + ovrpTextureFormat_R16G16B16A16_FP = 2, + ovrpTextureFormat_R11G11B10_FP = 3, + ovrpTextureFormat_B8G8R8A8_sRGB = 4, + ovrpTextureFormat_B8G8R8A8 = 5, + ovrpTextureFormat_R5G6B5 = 11, + ovrpTextureFormat_R16G16_FP = 12, + ovrpTextureFormat_A2B10G10R10 = 13, + + // depth texture formats + ovrpTextureFormat_D16 = 6, + ovrpTextureFormat_D24_S8 = 7, + ovrpTextureFormat_D32_FP = 8, + ovrpTextureFormat_D32_FP_S8 = 9, + + ovrpTextureFormat_None = 10, + + ovrpTextureFormat_EnumSize = 0x7fffffff +} ovrpTextureFormat; + +/// Flags used by ovrpLayerDesc +typedef enum { + /// Only create a single stage + ovrpLayerFlag_Static = (1 << 0), + /// Boost CPU priority while visible + ovrpLayerFlag_LoadingScreen = (1 << 1), + /// Force fov to be symmetric + ovrpLayerFlag_SymmetricFov = (1 << 2), + /// Texture origin is in bottom-left + ovrpLayerFlag_TextureOriginAtBottomLeft = (1 << 3), + /// Correct for chromatic aberration, deprecated + ovrpLayerFlag_ChromaticAberrationCorrection = (1 << 4), + /// Does not allocate texture space within the swapchain + ovrpLayerFlag_NoAllocation = (1 << 5), + /// Enable protected content, added in 1.23 + ovrpLayerFlag_ProtectedContent = (1 << 6), + /// Allocate AndroidSurfaceSwapChain, instead of regular TextureSwapChain + ovrpLayerFlag_AndroidSurfaceSwapChain = (1 << 7), + /// Allocate SpaceWarp data with m_textureSwapChain together + ovrpLayerFlag_SpaceWarpDataAllocation = (1 << 8), + ovrpLayerFlag_SpaceWarpDedicatedDepth = (1 << 9), + /// VrApi flag: VRAPI_ANDROID_SURFACE_SWAP_CHAIN_FLAG_SYNCHRONOUS + ovrpLayerFlag_Synchronous = (1 << 10), + /// VrApi flag:VRAPI_ANDROID_SURFACE_SWAP_CHAIN_FLAG_USE_TIMESTAMPS + ovrpLayerFlag_UseTimestamps = (1 << 11), + /// Allocate layer using subsampled layout + ovrpLayerFlag_Subsampled = (1 << 12), + /// if non-static, allocate 4 elements in a swapchain + ovrpLayerFlag_4DeepSwapchain = (1 << 13), + /// Bicubic Filtering + ovrpLayerFlag_BicubicFiltering = (1 << 14), + /// The layer is created through ovrp_EnqueueSetupLayer() + ovrpLayerFlag_LegacyOverlay = (1 << 15), + + + + +} ovrpLayerFlags; + +/// Layer description used by ovrp_SetupLayer to create the layer +#define OVRP_LAYER_DESC \ + struct { \ + ovrpShape Shape; \ + ovrpLayout Layout; \ + ovrpSizei TextureSize; \ + int MipLevels; \ + int SampleCount; \ + ovrpTextureFormat Format; \ + int LayerFlags; \ + } + +typedef OVRP_LAYER_DESC ovrpLayerDesc; + +#define OVRP_LAYER_DESC_TYPE \ + union { \ + ovrpLayerDesc Base; \ + OVRP_LAYER_DESC; \ + } + +typedef OVRP_LAYER_DESC_TYPE ovrpLayerDesc_Quad; +typedef OVRP_LAYER_DESC_TYPE ovrpLayerDesc_Cylinder; +typedef OVRP_LAYER_DESC_TYPE ovrpLayerDesc_Cubemap; + +typedef struct { + OVRP_LAYER_DESC_TYPE; + ovrpFovf Fov[ovrpEye_Count]; + ovrpRectf VisibleRect[ovrpEye_Count]; + ovrpSizei MaxViewportSize; + // added for 1.17 + ovrpTextureFormat DepthFormat; + + // added for 1.49 + ovrpTextureFormat MotionVectorFormat; + ovrpTextureFormat MotionVectorDepthFormat; + ovrpSizei MotionVectorTextureSize; +} ovrpLayerDesc_EyeFov; + +typedef OVRP_LAYER_DESC_TYPE ovrpLayerDesc_OffcenterCubemap; +typedef OVRP_LAYER_DESC_TYPE ovrpLayerDesc_Equirect; + + + + + + + + + + +typedef struct { + OVRP_LAYER_DESC_TYPE; + + + +} ovrpLayerDesc_InsightPassthrough; + +typedef OVRP_LAYER_DESC_TYPE ovrpLayerDesc_Fisheye; + +typedef union { + OVRP_LAYER_DESC_TYPE; + ovrpLayerDesc_Quad Quad; + ovrpLayerDesc_Cylinder Cylinder; + ovrpLayerDesc_Cubemap Cubemap; + ovrpLayerDesc_EyeFov EyeFov; + ovrpLayerDesc_OffcenterCubemap OffcenterCubemap; + ovrpLayerDesc_Equirect Equirect; + ovrpLayerDesc_Fisheye Fisheye; + ovrpLayerDesc_InsightPassthrough InsightPassthrough; +} ovrpLayerDescUnion; + +#undef OVRP_LAYER_DESC +#undef OVRP_LAYER_DESC_TYPE + +/// Flags used by ovrpLayerSubmit +typedef enum { + /// Pose relative to head + ovrpLayerSubmitFlag_HeadLocked = (1 << 0), + /// Layer is octilinear (LMS) + ovrpLayerSubmitFlag_Octilinear = (1 << 1), + /// Use reverse Z + ovrpLayerSubmitFlag_ReverseZ = (1 << 2), + /// Disable layer depth compositing on Rift + ovrpLayerSubmitFlag_NoDepth = (1 << 3), + /// Use inverse alpha for timewarp blending + ovrpLayerSubmitFlag_InverseAlpha = (1 << 4), + /// Combine the submitted layer with the layers generated from OVROverlay commands + ovrpLayerSubmitFlag_CombineLayerSubmits = (1 << 5), + /// Enable VrApi "Expensive" SuperSample Flag. + ovrpLayerSubmitFlag_ExpensiveSuperSample = (1 << 8), + /// Enable per-overlay show/hide functionality. + ovrpLayerSubmitFlag_Hidden = (1 << 9), + /// Force the texture's alpha to 1.0 on Rift + ovrpLayerSubmitFlag_IgnoreSourceAlpha = (1 << 10), + /// Enable Space warp on Fov layer + ovrpLayerSubmitFlag_SpaceWarp = (1 << 11), + /// An efficient version of ovrpLayerSubmitFlag_ExpensiveSuperSample + ovrpLayerSubmitFlag_EfficientSuperSample = (1 << 12), + /// Applies a post-distortion space sharpening filtering + ovrpLayerSubmitFlag_EfficientSharpen = (1 << 13), + /// Be used as a placeholder in combining native layers which was created outside OVRPlugin + ovrpLayerSubmitFlag_ExternalNativeLayer = (1 << 14), + /// Layer submit flag version of bicubic filtering + ovrpLayerSubmitFlag_BicubicFiltering = (1 << 15), + // Higher quality but more costly version of ovrpLayerSubmitFlag_Sharpen + ovrpLayerSubmitFlag_QualitySharpen = (1 << 16), + // Layer submit flag version of secure content + ovrpLayerSubmitFlag_SecureContent = (1 << 17), +} ovrpLayerSubmitFlags; + +/// Factors used for source and dest alpha to make up the blend function. +typedef enum { + ovrpBlendFactorZero = 0, + ovrpBlendFactorOne = 1, + ovrpBlendFactorSrcAlpha = 2, + ovrpBlendFactorOneMinusSrcAlpha = 3, + ovrpBlendFactorDstAlpha = 4, + ovrpBlendFactorOneMinusDstAlpha = 5 +} ovrpBlendFactor; + +/// Layer state to submit to ovrp_EndFrame +#define OVRP_LAYER_SUBMIT \ + struct { \ + int LayerId; \ + int TextureStage; \ + ovrpRecti ViewportRect[ovrpEye_Count]; \ + ovrpPosef Pose; \ + int LayerSubmitFlags; \ + /* Added in 1.31 */ \ + ovrpVector4f ColorScale; \ + ovrpVector4f ColorOffset; \ + /* Added in 1.34 */ \ + ovrpBool OverrideTextureRectMatrix; \ + ovrpTextureRectMatrixf TextureRectMatrix; \ + ovrpBool OverridePerLayerColorScaleAndOffset; \ + /* Added in 1.60 */ \ + /* If blend factors are present (signaled by `HasBlendFactors == true`),*/ \ + /* they override the default blend function and all other influences */ \ + /* like the layer submit flags `ovrpLayerSubmitFlag_InverseAlpha` and */ \ + /* `ovrpLayerSubmitFlag_IgnoreSourceAlpha`. */ \ + /* Blend factors are not supported by CAPI and are ignored in the CAPI */ \ + /* implementation. */ \ + ovrpBool HasBlendFactors; \ + ovrpBlendFactor SrcBlendFactor; \ + ovrpBlendFactor DstBlendFactor; \ + } + +typedef OVRP_LAYER_SUBMIT ovrpLayerSubmit; + +#define OVRP_LAYER_SUBMIT_TYPE \ + union { \ + ovrpLayerSubmit Base; \ + OVRP_LAYER_SUBMIT; \ + } + +typedef struct { + OVRP_LAYER_SUBMIT_TYPE; + ovrpSizef Size; +} ovrpLayerSubmit_Quad; + +typedef struct { + OVRP_LAYER_SUBMIT_TYPE; + float ArcWidth; + float Height; + float Radius; +} ovrpLayerSubmit_Cylinder; + +typedef OVRP_LAYER_SUBMIT_TYPE ovrpLayerSubmit_Cubemap; + +typedef struct { + OVRP_LAYER_SUBMIT_TYPE; + // added in 1.18 + ovrpOctilinearLayout OctilinearLayout[ovrpEye_Count]; + float DepthNear; + float DepthFar; + // added in 1.44 + ovrpFovf Fov[ovrpEye_Count]; + // added in 1.49 + float MotionVectorDepthNear; + float MotionVectorDepthFar; + ovrpVector4f MotionVectorScale; + ovrpVector4f MotionVectorOffset; + ovrpPosef AppSpaceDeltaPose; +} ovrpLayerSubmit_EyeFov; + +typedef OVRP_LAYER_SUBMIT_TYPE ovrpLayerSubmit_OffcenterCubemap; +typedef OVRP_LAYER_SUBMIT_TYPE ovrpLayerSubmit_Equirect; + +typedef struct { + OVRP_LAYER_SUBMIT_TYPE; + float FovX; + float FovY; + float Horizon; + float Meridian; +} ovrpLayerSubmit_Fisheye; + +typedef union { + OVRP_LAYER_SUBMIT_TYPE; + ovrpLayerSubmit_Quad Quad; + ovrpLayerSubmit_Cylinder Cylinder; + ovrpLayerSubmit_Cubemap Cubemap; + ovrpLayerSubmit_EyeFov EyeFov; + ovrpLayerSubmit_OffcenterCubemap OffcenterCubemap; + ovrpLayerSubmit_Equirect Equirect; + ovrpLayerSubmit_Fisheye Fisheye; +} ovrpLayerSubmitUnion; + +typedef enum { + ovrpViewportStencilType_HiddenArea = 0, + ovrpViewportStencilType_VisibleArea = 1, + ovrpViewportStencilType_BorderLine = 2, + ovrpViewportStencilType_VisibleRectangle = 3, +} ovrpViewportStencilType; + +#undef OVRP_LAYER_SUBMIT +#undef OVRP_LAYER_SUBMIT_TYPE + +//----------------------------------------------------------------- +// Hand tracking +//----------------------------------------------------------------- + +typedef enum ovrpHandStatus_ { + ovrpHandStatus_HandTracked = (1 << 0), // hand is currently tracked by hand tracking + ovrpHandStatus_InputValid = (1 << 1), // if this is set the pointer pose and pinch data is usable + ovrpHandStatus_SystemGestureInProgress = (1 << 6), // if this is set the user is performing the system gesture + ovrpHandStatus_DominantHand = (1 << 7), // if this is set the hand is considered the dominant hand + ovrpHandStatus_MenuPressed = + (1 << 8), // if this is set the hand performed the system gesture as the non-dominant hand + ovrpHandStatus_EnumSize = 0x7fffffff +} ovrpHandStatus; + +typedef enum ovrpHandFinger_ { + ovrpHandFinger_Thumb = 0, + ovrpHandFinger_Index = 1, + ovrpHandFinger_Middle = 2, + ovrpHandFinger_Ring = 3, + ovrpHandFinger_Pinky = 4, + ovrpHandFinger_Max, + ovrpHandFinger_EnumSize = 0x7fffffff +} ovrpHandFinger; + +// clang-format off +typedef enum ovrpHandFingerPinch_ { + ovrpHandFingerPinch_Thumb = (1 << ovrpHandFinger_Thumb), + ovrpHandFingerPinch_Index = (1 << ovrpHandFinger_Index), + ovrpHandFingerPinch_Middle = (1 << ovrpHandFinger_Middle), + ovrpHandFingerPinch_Ring = (1 << ovrpHandFinger_Ring), + ovrpHandFingerPinch_Pinky = (1 << ovrpHandFinger_Pinky), + ovrpHandFingerPinch_Max, + ovrpHandFingerPinch_EnumSize = 0x7fffffff +} ovrpHandFingerPinch; +// clang-format on + +// clang-format off +typedef enum ovrpBoneId_ { + ovrpBoneId_Invalid = -1, + + // hand bones + ovrpBoneId_Hand_Start = 0, + ovrpBoneId_Hand_WristRoot = ovrpBoneId_Hand_Start + 0, // root frame of the hand, where the wrist is located + ovrpBoneId_Hand_ForearmStub = ovrpBoneId_Hand_Start + 1, // frame for user's forearm + ovrpBoneId_Hand_Thumb0 = ovrpBoneId_Hand_Start + 2, // thumb trapezium bone + ovrpBoneId_Hand_Thumb1 = ovrpBoneId_Hand_Start + 3, // thumb metacarpal bone + ovrpBoneId_Hand_Thumb2 = ovrpBoneId_Hand_Start + 4, // thumb proximal phalange bone + ovrpBoneId_Hand_Thumb3 = ovrpBoneId_Hand_Start + 5, // thumb distal phalange bone + ovrpBoneId_Hand_Index1 = ovrpBoneId_Hand_Start + 6, // index proximal phalange bone + ovrpBoneId_Hand_Index2 = ovrpBoneId_Hand_Start + 7, // index intermediate phalange bone + ovrpBoneId_Hand_Index3 = ovrpBoneId_Hand_Start + 8, // index distal phalange bone + ovrpBoneId_Hand_Middle1 = ovrpBoneId_Hand_Start + 9, // middle proximal phalange bone + ovrpBoneId_Hand_Middle2 = ovrpBoneId_Hand_Start + 10, // middle intermediate phalange bone + ovrpBoneId_Hand_Middle3 = ovrpBoneId_Hand_Start + 11, // middle distal phalange bone + ovrpBoneId_Hand_Ring1 = ovrpBoneId_Hand_Start + 12, // ring proximal phalange bone + ovrpBoneId_Hand_Ring2 = ovrpBoneId_Hand_Start + 13, // ring intermediate phalange bone + ovrpBoneId_Hand_Ring3 = ovrpBoneId_Hand_Start + 14, // ring distal phalange bone + ovrpBoneId_Hand_Pinky0 = ovrpBoneId_Hand_Start + 15, // pinky metacarpal bone + ovrpBoneId_Hand_Pinky1 = ovrpBoneId_Hand_Start + 16, // pinky proximal phalange bone + ovrpBoneId_Hand_Pinky2 = ovrpBoneId_Hand_Start + 17, // pinky intermediate phalange bone + ovrpBoneId_Hand_Pinky3 = ovrpBoneId_Hand_Start + 18, // pinky distal phalange bone + ovrpBoneId_Hand_MaxSkinnable = ovrpBoneId_Hand_Start + 19, + // Bone tips are position only. They are not used for skinning but useful for hit-testing. + // NOTE: ovrBoneId_Hand_ThumbTip == ovrBoneId_Hand_MaxSkinnable since the extended tips need to be contiguous + ovrpBoneId_Hand_ThumbTip = ovrpBoneId_Hand_MaxSkinnable + 0, // tip of the thumb + ovrpBoneId_Hand_IndexTip = ovrpBoneId_Hand_MaxSkinnable + 1, // tip of the index finger + ovrpBoneId_Hand_MiddleTip = ovrpBoneId_Hand_MaxSkinnable + 2, // tip of the middle finger + ovrpBoneId_Hand_RingTip = ovrpBoneId_Hand_MaxSkinnable + 3, // tip of the ring finger + ovrpBoneId_Hand_PinkyTip = ovrpBoneId_Hand_MaxSkinnable + 4, // tip of the pinky + ovrpBoneId_Hand_End = ovrpBoneId_Hand_MaxSkinnable + 5, + + // body bones + ovrpBoneId_Body_Start = 0, + ovrpBoneId_Body_Root = ovrpBoneId_Body_Start + 0, + ovrpBoneId_Body_Hips = ovrpBoneId_Body_Start + 1, + ovrpBoneId_Body_SpineLower = ovrpBoneId_Body_Start + 2, + ovrpBoneId_Body_SpineMiddle = ovrpBoneId_Body_Start + 3, + ovrpBoneId_Body_SpineUpper = ovrpBoneId_Body_Start + 4, + ovrpBoneId_Body_Chest = ovrpBoneId_Body_Start + 5, + ovrpBoneId_Body_Neck = ovrpBoneId_Body_Start + 6, + ovrpBoneId_Body_Head = ovrpBoneId_Body_Start + 7, + ovrpBoneId_Body_LeftShoulder = ovrpBoneId_Body_Start + 8, + ovrpBoneId_Body_LeftScapula = ovrpBoneId_Body_Start + 9, + ovrpBoneId_Body_LeftArmUpper = ovrpBoneId_Body_Start + 10, + ovrpBoneId_Body_LeftArmLower = ovrpBoneId_Body_Start + 11, + ovrpBoneId_Body_LeftHandWristTwist = ovrpBoneId_Body_Start + 12, + ovrpBoneId_Body_RightShoulder = ovrpBoneId_Body_Start + 13, + ovrpBoneId_Body_RightScapula = ovrpBoneId_Body_Start + 14, + ovrpBoneId_Body_RightArmUpper = ovrpBoneId_Body_Start + 15, + ovrpBoneId_Body_RightArmLower = ovrpBoneId_Body_Start + 16, + ovrpBoneId_Body_RightHandWristTwist = ovrpBoneId_Body_Start + 17, + ovrpBoneId_Body_LeftHandPalm = ovrpBoneId_Body_Start + 18, + ovrpBoneId_Body_LeftHandWrist = ovrpBoneId_Body_Start + 19, + ovrpBoneId_Body_LeftHandThumbMetacarpal = ovrpBoneId_Body_Start + 20, + ovrpBoneId_Body_LeftHandThumbProximal = ovrpBoneId_Body_Start + 21, + ovrpBoneId_Body_LeftHandThumbDistal = ovrpBoneId_Body_Start + 22, + ovrpBoneId_Body_LeftHandThumbTip = ovrpBoneId_Body_Start + 23, + ovrpBoneId_Body_LeftHandIndexMetacarpal = ovrpBoneId_Body_Start + 24, + ovrpBoneId_Body_LeftHandIndexProximal = ovrpBoneId_Body_Start + 25, + ovrpBoneId_Body_LeftHandIndexIntermediate = ovrpBoneId_Body_Start + 26, + ovrpBoneId_Body_LeftHandIndexDistal = ovrpBoneId_Body_Start + 27, + ovrpBoneId_Body_LeftHandIndexTip = ovrpBoneId_Body_Start + 28, + ovrpBoneId_Body_LeftHandMiddleMetacarpal = ovrpBoneId_Body_Start + 29, + ovrpBoneId_Body_LeftHandMiddleProximal = ovrpBoneId_Body_Start + 30, + ovrpBoneId_Body_LeftHandMiddleIntermediate = ovrpBoneId_Body_Start + 31, + ovrpBoneId_Body_LeftHandMiddleDistal = ovrpBoneId_Body_Start + 32, + ovrpBoneId_Body_LeftHandMiddleTip = ovrpBoneId_Body_Start + 33, + ovrpBoneId_Body_LeftHandRingMetacarpal = ovrpBoneId_Body_Start + 34, + ovrpBoneId_Body_LeftHandRingProximal = ovrpBoneId_Body_Start + 35, + ovrpBoneId_Body_LeftHandRingIntermediate = ovrpBoneId_Body_Start + 36, + ovrpBoneId_Body_LeftHandRingDistal = ovrpBoneId_Body_Start + 37, + ovrpBoneId_Body_LeftHandRingTip = ovrpBoneId_Body_Start + 38, + ovrpBoneId_Body_LeftHandLittleMetacarpal = ovrpBoneId_Body_Start + 39, + ovrpBoneId_Body_LeftHandLittleProximal = ovrpBoneId_Body_Start + 40, + ovrpBoneId_Body_LeftHandLittleIntermediate = ovrpBoneId_Body_Start + 41, + ovrpBoneId_Body_LeftHandLittleDistal = ovrpBoneId_Body_Start + 42, + ovrpBoneId_Body_LeftHandLittleTip = ovrpBoneId_Body_Start + 43, + ovrpBoneId_Body_RightHandPalm = ovrpBoneId_Body_Start + 44, + ovrpBoneId_Body_RightHandWrist = ovrpBoneId_Body_Start + 45, + ovrpBoneId_Body_RightHandThumbMetacarpal = ovrpBoneId_Body_Start + 46, + ovrpBoneId_Body_RightHandThumbProximal = ovrpBoneId_Body_Start + 47, + ovrpBoneId_Body_RightHandThumbDistal = ovrpBoneId_Body_Start + 48, + ovrpBoneId_Body_RightHandThumbTip = ovrpBoneId_Body_Start + 49, + ovrpBoneId_Body_RightHandIndexMetacarpal = ovrpBoneId_Body_Start + 50, + ovrpBoneId_Body_RightHandIndexProximal = ovrpBoneId_Body_Start + 51, + ovrpBoneId_Body_RightHandIndexIntermediate = ovrpBoneId_Body_Start + 52, + ovrpBoneId_Body_RightHandIndexDistal = ovrpBoneId_Body_Start + 53, + ovrpBoneId_Body_RightHandIndexTip = ovrpBoneId_Body_Start + 54, + ovrpBoneId_Body_RightHandMiddleMetacarpal = ovrpBoneId_Body_Start + 55, + ovrpBoneId_Body_RightHandMiddleProximal = ovrpBoneId_Body_Start + 56, + ovrpBoneId_Body_RightHandMiddleIntermediate = ovrpBoneId_Body_Start + 57, + ovrpBoneId_Body_RightHandMiddleDistal = ovrpBoneId_Body_Start + 58, + ovrpBoneId_Body_RightHandMiddleTip = ovrpBoneId_Body_Start + 59, + ovrpBoneId_Body_RightHandRingMetacarpal = ovrpBoneId_Body_Start + 60, + ovrpBoneId_Body_RightHandRingProximal = ovrpBoneId_Body_Start + 61, + ovrpBoneId_Body_RightHandRingIntermediate = ovrpBoneId_Body_Start + 62, + ovrpBoneId_Body_RightHandRingDistal = ovrpBoneId_Body_Start + 63, + ovrpBoneId_Body_RightHandRingTip = ovrpBoneId_Body_Start + 64, + ovrpBoneId_Body_RightHandLittleMetacarpal = ovrpBoneId_Body_Start + 65, + ovrpBoneId_Body_RightHandLittleProximal = ovrpBoneId_Body_Start + 66, + ovrpBoneId_Body_RightHandLittleIntermediate = ovrpBoneId_Body_Start + 67, + ovrpBoneId_Body_RightHandLittleDistal = ovrpBoneId_Body_Start + 68, + ovrpBoneId_Body_RightHandLittleTip = ovrpBoneId_Body_Start + 69, + ovrpBoneId_Body_End = ovrpBoneId_Body_Start + 70, + + // add other skeleton bone definitions here... + + ovrpBoneId_Max = (ovrpBoneId_Hand_End > ovrpBoneId_Body_End) ? ovrpBoneId_Hand_End : ovrpBoneId_Body_End, + ovrpBoneId_EnumSize = 0x7fff +} ovrpBoneId; +// clang-format on + +//----------------------------------------------------------------- +// Hand skeleton + +// ovrBoneCapsule +// _---_ +// -" "- +// / \ +// |----A----| +// | | | +// | | | +// | |-r->| +// | | | +// | | | +// |----B----| +// \ / +// -. .- +// '---' +typedef struct ovrpBoneCapsule_ { + short BoneIndex; + // Points at either end of the cylinder inscribed in the capsule. Also the center points for + // spheres at either end of the capsule. Points A and B in the diagram above. + ovrpVector3f Points[2]; + // The radius of the capsule cylinder and of the half-sphere caps on the ends of the capsule. + float Radius; +} ovrpBoneCapsule; + +typedef struct ovrpBone_ { + ovrpBoneId BoneId; + // index of this bone's parent bone (-1 if no parent) + short ParentBoneIndex; + ovrpPosef Pose; +} ovrpBone; + +typedef enum ovrpSkeletonConstants_ { + ovrpSkeletonConstants_MaxHandBones = ovrpBoneId_Hand_End, + ovrpSkeletonConstants_MaxBodyBones = ovrpBoneId_Body_End, + ovrpSkeletonConstants_MaxBones = ovrpBoneId_Max, + ovrpSkeletonConstants_MaxBoneCapsules = 19, + ovrpSkeletonConstants_EnumSize = 0x7fffffff +} ovrpSkeletonConstants; + +/// Identifies a skeleton type. +typedef enum ovrpSkeletonType_ { + ovrpSkeletonType_None = -1, + ovrpSkeletonType_HandLeft = 0, + ovrpSkeletonType_HandRight = 1, + ovrpSkeletonType_Body = 2, + ovrpSkeletonType_Count, + ovrpSkeletonType_EnumSize = 0x7fffffff +} ovrpSkeletonType; + +typedef struct ovrpSkeleton2_ { + ovrpSkeletonType SkeletonType; + unsigned int NumBones; + unsigned int NumBoneCapsules; + ovrpBone Bones[ovrpSkeletonConstants_MaxBones]; + ovrpBoneCapsule BoneCapsules[ovrpSkeletonConstants_MaxBoneCapsules]; +} ovrpSkeleton2; + +//----------------------------------------------------------------- +// Hand mesh + +typedef enum ovrpMeshConstants_ { + ovrpMesh_MaxVertices = 3000, + ovrpMesh_MaxIndices = ovrpMesh_MaxVertices * 6, + ovrpMesh_EnumSize = 0x7fffffff +} ovrpMeshConstants; + +/// Identifies a mesh type. +typedef enum ovrpMeshType_ { + ovrpMeshType_None = -1, + ovrpMeshType_HandLeft = 0, + ovrpMeshType_HandRight = 1, + ovrpMeshType_Count, + ovrpMeshType_EnumSize = 0x7fffffff +} ovrpMeshType; + + +typedef struct ovrpMesh_ { + // Type of mesh this data describes. + ovrpMeshType MeshType; + // Number of unique vertices in the mesh. + unsigned int NumVertices; + // Number of unique indices in the mesh. + unsigned int NumIndices; + // An array of count NumVertices positions for each vertex. Always valid. + ovrpVector3f VertexPositions[ovrpMesh_MaxVertices]; + // An array of count NumIndices of vertex indices specifying triangles that make up the mesh. Always valid. + ovrpInt16 Indices[ovrpMesh_MaxIndices]; + // An array of count NumVertices of normals for each vertex. + // If null, this attribute is not used. + ovrpVector3f VertexNormals[ovrpMesh_MaxVertices]; + // An array of count NumVertices of texture coordinates for each vertex. + // If null, this attribute is not used. + ovrpVector2f VertexUV0[ovrpMesh_MaxVertices]; + // An array of count NumVertices of blend indices for each of the bones that each vertex is weighted to. + // Always valid. An index of < 0 means no blend weight. + ovrpVector4s BlendIndices[ovrpMesh_MaxVertices]; + // An array of count NumVertices of weights for each of the bones affecting each vertex. Always valid. + ovrpVector4f BlendWeights[ovrpMesh_MaxVertices]; +} ovrpMesh; + +//----------------------------------------------------------------- +// Hand pose +typedef enum ovrpTrackingConfidence_ { + ovrpTrackingConfidence_Low = 0, + ovrpTrackingConfidence_High = 0x3f800000, + ovrpTrackingConfidence_EnumSize = 0x7fffffff +} ovrpTrackingConfidence; + +typedef struct ovrpHandState_ { + // Hand Status bitfield described by ovrpHandStatus flags. + unsigned int Status; + + // Root pose of the hand in world space. Not to be confused with the root bone's transform. + // The root bone can still be offset from this by the skeleton's rest pose. + ovrpPosef RootPose; + + // Current rotation of each bone. + ovrpQuatf BoneRotations[ovrpSkeletonConstants_MaxHandBones]; + + // Provides a bitmask indicating if each finger is "pinched" or not. Indexable via bitshifting with the ovrpHandFinger + // enum i.e. (1 << ovrpHandFinger_Index) + unsigned int Pinches; + + // Provides a 0.0f to 1.0f value of how "pinched" each finger is. Indexable via the ovrpHandFinger enum. + float PinchStrength[ovrpHandFinger_Max]; + + // World space position and translation of the pointer attached to the hand. + ovrpPosef PointerPose; + + float HandScale; + + // Tracking confidence. Range [0,1], 0.0 = lowest confidence, 1.0 = highest confidence. + // This is useful for smoothly de-emphasizing hands as confidence decreases. + // This is the amount of confidence that the system has that the entire hand pose is correct. + ovrpTrackingConfidence HandConfidence; + + // Per-finger tracking confidence. Range [0,1], 0.0 = lowest confidence, 1.0 = highest confidence. + // This is the amount of confidence the system has that the individual finger poses are correct. + ovrpTrackingConfidence FingerConfidences[ovrpHandFinger_Max]; + + // Time stamp for the pose that was requested in global system time. + double RequestedTimeStamp; + + // Time stamp of the captured sample that the pose was extrapolated from. + double SampleTimeStamp; +} ovrpHandState; + +typedef struct ovrpBodyJointLocation_ { + ovrpUInt64 LocationFlags; + ovrpPosef Pose; +} ovrpBodyJointLocation; + +typedef struct ovrpBodyState_ { + ovrpBool IsActive; + float Confidence; + ovrpUInt32 SkeletonChangedCount; + double Time; + ovrpBodyJointLocation JointLocations[ovrpBoneId_Body_End]; +} ovrpBodyState; + +typedef enum ovrpFaceExpression_ { + ovrpFaceExpression_Invalid = -1, + ovrpFaceExpression_Brow_Lowerer_L = 0, + ovrpFaceExpression_Brow_Lowerer_R = 1, + ovrpFaceExpression_Cheek_Puff_L = 2, + ovrpFaceExpression_Cheek_Puff_R = 3, + ovrpFaceExpression_Cheek_Raiser_L = 4, + ovrpFaceExpression_Cheek_Raiser_R = 5, + ovrpFaceExpression_Cheek_Suck_L = 6, + ovrpFaceExpression_Cheek_Suck_R = 7, + ovrpFaceExpression_Chin_Raiser_B = 8, + ovrpFaceExpression_Chin_Raiser_T = 9, + ovrpFaceExpression_Dimpler_L = 10, + ovrpFaceExpression_Dimpler_R = 11, + ovrpFaceExpression_Eyes_Closed_L = 12, + ovrpFaceExpression_Eyes_Closed_R = 13, + ovrpFaceExpression_Eyes_Look_Down_L = 14, + ovrpFaceExpression_Eyes_Look_Down_R = 15, + ovrpFaceExpression_Eyes_Look_Left_L = 16, + ovrpFaceExpression_Eyes_Look_Left_R = 17, + ovrpFaceExpression_Eyes_Look_Right_L = 18, + ovrpFaceExpression_Eyes_Look_Right_R = 19, + ovrpFaceExpression_Eyes_Look_Up_L = 20, + ovrpFaceExpression_Eyes_Look_Up_R = 21, + ovrpFaceExpression_Inner_Brow_Raiser_L = 22, + ovrpFaceExpression_Inner_Brow_Raiser_R = 23, + ovrpFaceExpression_Jaw_Drop = 24, + ovrpFaceExpression_Jaw_Sideways_Left = 25, + ovrpFaceExpression_Jaw_Sideways_Right = 26, + ovrpFaceExpression_Jaw_Thrust = 27, + ovrpFaceExpression_Lid_Tightener_L = 28, + ovrpFaceExpression_Lid_Tightener_R = 29, + ovrpFaceExpression_Lip_Corner_Depressor_L = 30, + ovrpFaceExpression_Lip_Corner_Depressor_R = 31, + ovrpFaceExpression_Lip_Corner_Puller_L = 32, + ovrpFaceExpression_Lip_Corner_Puller_R = 33, + ovrpFaceExpression_Lip_Funneler_LB = 34, + ovrpFaceExpression_Lip_Funneler_LT = 35, + ovrpFaceExpression_Lip_Funneler_RB = 36, + ovrpFaceExpression_Lip_Funneler_RT = 37, + ovrpFaceExpression_Lip_Pressor_L = 38, + ovrpFaceExpression_Lip_Pressor_R = 39, + ovrpFaceExpression_Lip_Pucker_L = 40, + ovrpFaceExpression_Lip_Pucker_R = 41, + ovrpFaceExpression_Lip_Stretcher_L = 42, + ovrpFaceExpression_Lip_Stretcher_R = 43, + ovrpFaceExpression_Lip_Suck_LB = 44, + ovrpFaceExpression_Lip_Suck_LT = 45, + ovrpFaceExpression_Lip_Suck_RB = 46, + ovrpFaceExpression_Lip_Suck_RT = 47, + ovrpFaceExpression_Lip_Tightener_L = 48, + ovrpFaceExpression_Lip_Tightener_R = 49, + ovrpFaceExpression_Lips_Toward = 50, + ovrpFaceExpression_Lower_Lip_Depressor_L = 51, + ovrpFaceExpression_Lower_Lip_Depressor_R = 52, + ovrpFaceExpression_Mouth_Left = 53, + ovrpFaceExpression_Mouth_Right = 54, + ovrpFaceExpression_Nose_Wrinkler_L = 55, + ovrpFaceExpression_Nose_Wrinkler_R = 56, + ovrpFaceExpression_Outer_Brow_Raiser_L = 57, + ovrpFaceExpression_Outer_Brow_Raiser_R = 58, + ovrpFaceExpression_Upper_Lid_Raiser_L = 59, + ovrpFaceExpression_Upper_Lid_Raiser_R = 60, + ovrpFaceExpression_Upper_Lip_Raiser_L = 61, + ovrpFaceExpression_Upper_Lip_Raiser_R = 62, + ovrpFaceExpression_Max = 63, + ovrpFaceExpression_EnumSize = 0x7FFFFFFF +} ovrpFaceExpression; + +typedef enum ovrpFaceConfidence_ { + ovrpFaceConfidence_Lower = 0, + ovrpFaceConfidence_Upper = 1, + ovrpFaceConfidence_Max = 2, + ovrpFaceConfidence_None = -1, + ovrpFaceConfidence_EnumSize = 0x7FFFFFFF +} ovrpFaceConfidence; + +typedef enum ovrpFaceConstants_ { + ovrpFaceConstants_MaxFaceExpressions = ovrpFaceExpression_Max, + ovrpFaceConstants_MaxFaceConfidenceWeights = ovrpFaceConfidence_Max, + ovrpFaceConstants_EnumSize = 0x7fffffff +} ovrpFaceConstants; + +typedef struct ovrpFaceExpressionStatus_ { + ovrpBool IsValid; + ovrpBool IsEyeFollowingBlendshapesValid; +} ovrpFaceExpressionStatus; + +typedef struct ovrpFaceState_ { + float ExpressionWeights[ovrpFaceConstants_MaxFaceExpressions]; + float ExpressionWeightConfidences[ovrpFaceConstants_MaxFaceConfidenceWeights]; + ovrpFaceExpressionStatus Status; + double Time; +} ovrpFaceState; + +typedef struct ovrpEyeGazeState_ { + ovrpPosef Pose; + float Confidence; + ovrpBool IsValid; +} ovrpEyeGazeState; + +typedef struct ovrpEyeGazesState_ { + ovrpEyeGazeState EyeGazes[ovrpEye_Count]; + double Time; +} ovrpEyeGazesState; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//----------------------------------------------------------------- +// Color Space Management +//----------------------------------------------------------------- +/// Color space types for HMDs +/// +/// Until vrapi_SetClientColorDesc is called, the client will default to Rec2020 for Quest and +/// Rec709 for Go HMDs. +/// +/// This API only handles color-space remapping. Unless specified, all color spaces use D65 white +/// point. It will not affect brightness, contrast or gamma curves. Some of these aspects such as +/// gamma, is handled by the texture format being used. From the GPU samplers' point-of-view, each +/// texture will continue to be treated as linear luminance including sRGB which is converted to +/// linear by the texture sampler. +/// +/// 'VRAPI_COLORSPACE_UNMANAGED' will force the runtime to skip color correction for the provided +/// content. This is *not* recommended unless the app developer is sure about what they're doing. +/// 'VRAPI_COLORSPACE_UNMANAGED' is mostly useful for research & experimentation, but not for +/// software distribution. This is because unless the client is applying the necessary corrections +/// for each HMD type, the results seen in the HMD will be uncalibrated. This is especially true for +/// future HMDs where the color space is not yet known or defined, which could lead to colors that +/// look too dull, too saturated, or hue shifted. +/// +/// Although native Quest and Rift CV1 color spaces are provided as options, they are not +/// standardized color spaces. While we provide the exact color space primary coordinates, for +/// better standardized visualized of authored content, it's recommended that the developers master +/// using a well-defined color space in the provided in the options such as Rec.2020. +/// +/// It is also recommended that content be authored for the wider color spaces instead of Rec.709 to +/// prevent visuals from looking "washed out", "dull" or "desaturated" on wider gamut devices like +/// the Quest. +/// +/// Unique Color Space Details with Chromaticity Primaries in CIE 1931 xy: +/// +/// Color Space: P3, similar to DCI-P3, but using D65 white point instead. +/// Red : (0.680, 0.320) +/// Green: (0.265, 0.690) +/// Blue : (0.150, 0.060) +/// White: (0.313, 0.329) +/// +/// Color Space: Rift CV1 between P3 & Adobe RGB using D75 white point +/// Red : (0.666, 0.334) +/// Green: (0.238, 0.714) +/// Blue : (0.139, 0.053) +/// White: (0.298, 0.318) +/// +/// Color Space: Quest similar to Rift CV1 using D75 white point +/// Red : (0.661, 0.338) +/// Green: (0.228, 0.718) +/// Blue : (0.142, 0.042) +/// White: (0.298, 0.318) +/// +/// Color Space: Rift S similar to Rec 709 using D75 +/// Red : (0.640, 0.330) +/// Green: (0.292, 0.586) +/// Blue : (0.156, 0.058) +/// White: (0.298, 0.318) +/// +/// Note: Due to LCD limitations, the Go display will not be able to meaningfully differentiate +/// brightness levels below 13 out of 255 for 8-bit sRGB or 0.0015 out of 1.0 max for linear-RGB +/// shader output values. To that end, it is recommended that reliance on a dark and narrow gamut is +/// avoided, and the content is instead spread across a larger brightness range when possible. +/// +typedef enum ovrpColorSpace_ { + /// Default value until client sets calls SetClientColorDesc + ovrpColorSpace_Unknown = 0, + /// No color correction, not recommended for production use. See notes above for more info + ovrpColorSpace_Unmanaged = 1, + /// Preferred color space for standardized color across all Oculus HMDs with D65 white point + ovrpColorSpace_Rec_2020 = 2, + /// Rec. 709 is used on Oculus Go and shares the same primary color coordinates as sRGB + ovrpColorSpace_Rec_709 = 3, + /// Oculus Rift CV1 uses a unique color space, see enum description for more info + ovrpColorSpace_Rift_CV1 = 4, + /// Oculus Rift S uses a unique color space, see enum description for more info + ovrpColorSpace_Rift_S = 5, + /// Oculus Quest's native color space is slightly different than Rift CV1 + ovrpColorSpace_Quest = 6, + /// Similar to DCI-P3. See notes above for more details on P3 + ovrpColorSpace_P3 = 7, + /// Similar to sRGB but with deeper greens using D65 white point + ovrpColorSpace_Adobe_RGB = 8, + ovrpColorSpace_Count +} ovrpColorSpace; + +//----------------------------------------------------------------- +// Event Management +//----------------------------------------------------------------- +// Enum defining the type of the underlying event, required first element in every event struct +typedef enum ovrpEventType_ { + ovrpEventType_None = 0, + /// Refresh rate changed event + ovrpEventType_DisplayRefreshRateChange = 1, + + ovrpEventType_SpatialEntityCreateSpatialAnchorComplete = 49, // Deprecated + ovrpEventType_SpatialAnchorCreateComplete = 49, + + ovrpEventType_SpatialEntitySetComponentEnabledComplete = 50, // Deprecated + ovrpEventType_SpaceSetComponentStatusComplete = 50, + + ovrpEventType_SpatialEntityQueryResults = 51, // Deprecated + ovrpEventType_SpaceQueryResults = 51, + + ovrpEventType_SpatialEntityQueryComplete = 52, // Deprecated + ovrpEventType_SpaceQueryComplete = 52, + + ovrpEventType_SpatialEntityStorageSaveResult = 53, // Deprecated + ovrpEventType_SpaceSaveComplete = 53, + + ovrpEventType_SpatialEntityStorageEraseResult = 54, // Deprecated + ovrpEventType_SpaceEraseComplete = 54, + ovrpEventType_SpaceShareResult = 56, + ovrpEventType_SpaceListSaveResult = 57, + ovrpEventType_SceneCaptureComplete = 100, + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +} ovrpEventType; + +// biggest event that OVRPlugin can use +typedef struct ovrpEventDataBuffer_ { + ovrpEventType EventType; + unsigned char EventData[4000]; +} ovrpEventDataBuffer; + +typedef struct ovrpEventDisplayRefreshRateChange_ { + ovrpEventType EventType; + float FromRefreshRate; + float ToRefreshRate; +} ovrpEventDisplayRefreshRateChange; + +//----------------------------------------------------------------- +// Keyboard Tracking + + + + + + + + + + + + + + + + +#define OVRP_KEYBOARD_DESCRIPTION_NAME_LENGTH 128 + +// Enum defining the type of the keyboard model, effect render parameters and passthrough configuration. +typedef enum ovrpKeyboardPresentationStyles_ { + ovrpKeyboardPresentationStyles_Unknown = 0, + ovrpKeyboardPresentationStyles_Opaque = 1, + ovrpKeyboardPresentationStyles_KeyLabel = 2, +} ovrpKeyboardPresentationStyles; + +// Enum defining the type of the keyboard returned +typedef enum ovrpTrackedKeyboardFlags_ { + ovrpTrackedKeyboardFlags_Exists = 1, + ovrpTrackedKeyboardFlags_Local = 2, + ovrpTrackedKeyboardFlags_Remote = 4, + ovrpTrackedKeyboardFlags_Connected = 8, +} ovrpTrackedKeyboardFlags; + +// Enum defining the type of the keyboard requested +typedef enum ovrpTrackedKeyboardTrackingQueryFlags_ { + ovrpTrackedKeyboardQueryFlags_Local = 2, + ovrpTrackedKeyboardQueryFlags_Remote = 4, +} ovrpTrackedKeyboardQueryFlags; + +typedef struct ovrpKeyboardDescription_ { + // Tracked Object Name + char Name[OVRP_KEYBOARD_DESCRIPTION_NAME_LENGTH]; + + // Unique Object Identifier + ovrpUInt64 TrackedKeyboardId; + + // Keyboard Locale + ovrpVector3f Dimensions; + + // State of this keyboard + ovrpTrackedKeyboardFlags KeyboardFlags; + + // What type of rendering can be done for the model. + ovrpKeyboardPresentationStyles SupportedPresentationStyles; +} ovrpKeyboardDescription; + +typedef struct ovrpKeyboardState_ { + // Set to false if keyboard tracking is in an error state + ovrpBool IsActive; + + ovrpBool OrientationValid; + ovrpBool PositionValid; + ovrpBool OrientationTracked; + ovrpBool PositionTracked; + + // Position and orientation of keyboard + ovrpPoseStatef PoseState; + + // Contrast parameters, provided to Mixed Reality SDK for passthrough visualization + // when hands are over keyboard. (Will be deprecated in future.) + ovrpVector4f ContrastParameters; +} ovrpKeyboardState; + +// Keyboard Tracking Internal + + + + + + + + + + + + + + +#define OVRP_RENDER_MODEL_NAME_MAX_LENGTH 64 + +typedef struct ovrpRenderModelProperies_ { + char modelName[OVRP_RENDER_MODEL_NAME_MAX_LENGTH]; + ovrpUInt64 modelKey; + ovrpUInt32 vendorId; + ovrpUInt32 modelVersion; +} ovrpRenderModelProperties; + +// Enum defining the level of GLTF model supported by the application. +// Must match flags defined in arvr/projects/pcsdk/LibOVR/Client/OpenXR/Extensions/fb_render_model.h +typedef enum { + ovrpRenderModelFlags_SupportsGltf20Subset1 = 1, + ovrpRenderModelFlags_SupportsGltf20Subset2 = 2, + ovrpRenderModelFlags_EnumSize = 0x7fffffff +} ovrpRenderModelFlags; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//----------------------------------------------------------------- +// Insight Passthrough +//----------------------------------------------------------------- +typedef enum { + ovrpInsightPassthroughColorMapType_None = 0, + ovrpInsightPassthroughColorMapType_MonoToRgba = 1, + ovrpInsightPassthroughColorMapType_MonoToMono = 2, + ovrpInsightPassthroughColorMapType_HandsContrast = 3, + ovrpInsightPassthroughColorMapType_BrightnessContrastSaturation = 4, + + + + ovrpInsightPassthroughColorMapType_EnumSize = 0x7fffffff +} ovrpInsightPassthroughColorMapType; + +typedef enum { + ovrpInsightPassthroughStyleFlags_HasTextureOpacityFactor = 1 << 0, + ovrpInsightPassthroughStyleFlags_HasEdgeColor = 1 << 1, + ovrpInsightPassthroughStyleFlags_HasTextureColorMap = 1 << 2, + ovrpInsightPassthroughStyleFlags_EnumSize = 0x7fffffff +} ovrpInsightPassthroughStyleFlags; + +typedef struct { + /// The flags determine which fields of the struct have been initialize and + /// should be read. The values of fields which are not indicated to be + /// present by the flags should not be accessed. This is used to establish + /// backward: When new fields are added to the struct, callers of an older + /// version will only initialize the memory of previously known fields and + // indicate which ones those are in the flags. + ovrpInsightPassthroughStyleFlags Flags; + + /// Opacity of the (main) passthrough texture. + float TextureOpacityFactor; + + /// Color of the edge rendering effect. The effect is disabled if the alpha + /// value is set to 0. + ovrpColorf EdgeColor; + + /// The texture color map assigns a new color for each input (image) color. + /// The contents of `TextureColorMapData` is determined by + /// `TextureColorMapType`: + /// - For `MonoToRgba`, it is an array of 256 MrColorf values, i.e. one + /// float color tuple for each 8 bit grayscale input value. + /// - For `MonoToMono`, it is an array of 256 uint8 values, i.e. one + /// 8 bit grayscale output value for each input value. + /// - For `HandsContrast`, it is an array of 4 float values. + /// - For `BrightnessContrastSaturation`, it is an array of 3 float + /// values: [brightness, contrast, saturation]. + ovrpInsightPassthroughColorMapType TextureColorMapType; + unsigned int TextureColorMapDataSize; + unsigned char* TextureColorMapData; +} ovrpInsightPassthroughStyle; + +typedef enum { + ovrpInsightPassthroughCapabilityFlags_Passthrough = 1 << 0, + ovrpInsightPassthroughCapabilityFlags_Color = 1 << 1, + ovrpInsightPassthroughCapabilityFlags_Depth = 1 << 2, + ovrpInsightPassthroughCapabilityFlags_EnumSize = 0x7fffffff +} ovrpInsightPassthroughCapabilityFlags; + +//----------------------------------------------------------------- +// Insight Passthrough Keyboard Hands +//----------------------------------------------------------------- +typedef struct { + /// An intensity for left tracked hand. + /// An intensity value can be in the range [0.0, 1.0] where 0.0 is the lowest intensity. + float LeftHandIntensity; + + /// An intensity for right tracked hand. + /// An intensity value can be in the range [0.0, 1.0] where 0.0 is the lowest intensity. + float RightHandIntensity; +} ovrpInsightPassthroughKeyboardHandsIntensity; + +//----------------------------------------------------------------- +// Spatial Anchors + +typedef ovrpUInt64 ovrpSpace; +typedef ovrpUInt64 ovrpUser; +#define OVRP_SPACE_INVALID_HANDLE nullptr +#define OVRP_SPATIAL_ENTITY_UUID_SIZE 2 +#define OVRP_UUID_SIZE 16 +#define OVRP_SPACE_MAX_QUERY_RESULTS_PER_EVENT 128 + +// Components used by XrSpaces to determine what functionality they support +// - Locatable, enables location functionality for pose and orientation +// - Storable, enables save and erase functionality +// - Sharable, enables sharing off-device +// - Bounded2D, used in fb_scene extension +// - Bounded3D, used in fb_scene extension +// - SemanticLabels, used in fb_scene extension +// - RoomLayout, used in fb_scene extension +// - SpaceContainer, used in fb_spatial_entity_container extension +typedef enum { + ovrpSpatialEntityComponentType_Locatable = 0, // Deprecated + ovrpSpaceComponentType_Locatable = 0, + + ovrpSpatialEntityComponentType_Storable = 1, // Deprecated + ovrpSpaceComponentType_Storable = 1, + ovrpSpaceComponentType_Sharable = 2, + + ovrpSpaceComponentType_Bounded2D = 3, + ovrpSpaceComponentType_Bounded3D = 4, + ovrpSpaceComponentType_SemanticLabels = 5, + ovrpSpaceComponentType_RoomLayout = 6, + ovrpSpaceComponentType_SpaceContainer = 7, + + ovrpSpatialEntityComponentType_Max = 0x7ffffff, // Deprecated + ovrpSpaceComponentType_Max = 0x7ffffff, +} ovrpSpaceComponentType; + +// ovrpSpatialEntityComponentType is deprecated and replaced by ovrpSpaceComponentType +typedef ovrpSpaceComponentType ovrpSpatialEntityComponentType; + +// The storage location for the spatial entity +typedef enum { + ovrpSpaceStorageLocation_Invalid = 0, + ovrpSpaceStorageLocation_Local = 1, + ovrpSpaceStorageLocation_Cloud = 2, + ovrpSpaceStorageLocation_Max = 0x7ffffff, +} ovrpSpaceStorageLocation; + +typedef enum { + ovrpSpaceStoragePersistenceMode_Invalid = 0, + ovrpSpaceStoragePersistenceMode_Indefinite = 1, + ovrpSpaceStoragePersistenceMode_Max = 0x7ffffff, +} ovrpSpaceStoragePersistenceMode; + +// Action to be performed on queried items. +// - Load, Query for spaces and attempt a load on the spaces found. +// Successfully loaded spaces are returned. +typedef enum { + ovrpSpaceQueryActionType_Load = 0, +} ovrpSpaceQueryActionType; + +// Type of query to be performed +// - Action, Query for spaces using an ovrpSpaceQueryActionType +typedef enum { + ovrpSpaceQueryType_Action = 0, + ovrpSpaceQueryType_Max = 0x7ffffff, +} ovrpSpaceQueryType; + +// Filter to be used to narrow the queried spatial entities +// - None, Query for all spatial entities +// - Ids, Query for a single or a list of specific uuids +typedef enum { + ovrpSpaceQueryFilterType_None = 0, + ovrpSpaceQueryFilterType_Ids = 1, + ovrpSpaceQueryFilterType_Components = 2, + ovrpSpaceQueryFilterType_Max = 0x7ffffff, +} ovrpSpaceQueryFilterType; + +typedef struct { + ovrpTrackingOrigin trackingSpace; + ovrpPosef poseInSpace; + double time; +} ovrpSpatialAnchorCreateInfo; + +// New UUID type, uses ovrpByte for uint8_t +typedef struct ovrpUuid { + ovrpByte data[OVRP_UUID_SIZE]; +} ovrpUuid; + +typedef struct { + // list of uuids used for querying + ovrpUuid ids[1024]; + // size of the list + int numIds; +} ovrpSpaceFilterIdInfo; + +typedef struct { + // list of components used for querying + ovrpSpaceComponentType components[16]; + // size of the list + int numComponents; +} ovrpSpaceFilterComponentsInfo; + +typedef struct { + // type of query to be performed + ovrpSpaceQueryType queryType; + // maximum number of spaces to be returned + int maxQuerySpaces; + // timeout wait on query + double timeout; + // location we are querying for the spaces from + ovrpSpaceStorageLocation location; + // action to be performed on queried items if query type is + // of type ovrpSpaceQueryType_Action + ovrpSpaceQueryActionType actionType; + // type of filtering we wish to use on the spaces we've queried + ovrpSpaceQueryFilterType filterType; + // use only when filter type is ovrpSpaceQueryFilterType_Ids + ovrpSpaceFilterIdInfo IdInfo; + // use only when filter type is ovrpSpaceQueryFilterType_Components + ovrpSpaceFilterComponentsInfo componentsInfo; +} ovrpSpaceQueryInfo; + +typedef struct ovrpSpaceQueryResult { + ovrpSpace space; + ovrpUuid uuid; +} ovrpSpaceQueryResult; + +typedef struct ovrpEventDataSpatialAnchorCreateComplete_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpResult result; + ovrpSpace space; + ovrpUuid uuid; +} ovrpEventDataSpatialAnchorCreateComplete; + +typedef struct ovrpEventDataSpaceSetStatusComplete_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpResult result; + ovrpSpace space; + ovrpUuid uuid; + ovrpSpaceComponentType componentType; + ovrpBool enabled; +} ovrpEventDataSpaceSetStatusComplete; + +typedef struct ovrpEventSpaceQueryResults_ { + ovrpEventType EventType; + ovrpUInt64 requestId; +} ovrpEventSpaceQueryResults; + +typedef struct ovrpEventSpaceQueryComplete_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpResult result; +} ovrpEventSpaceQueryComplete; + +typedef struct ovrpEventSpaceStorageSaveResult_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpSpace space; + ovrpResult result; + ovrpUuid uuid; +} ovrpEventSpaceStorageSaveResult; + +typedef struct ovrpEventSpaceStorageEraseResult_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpResult result; + ovrpUuid uuid; + ovrpSpaceStorageLocation location; +} ovrpEventSpaceStorageEraseResult; + +typedef struct ovrpEventSpaceShareResult_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpResult result; +} ovrpEventSpaceShareResult; + +typedef struct ovrpEventSpaceListSaveResult_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpResult result; +} ovrpEventSpaceListSaveResult; + +typedef struct ovrpSpaceContainer_ { + // Input, capacity of UUID list. + int uuidCapacityInput; + // Output, number of spatial entities included in the list. + int uuidCountOutput; + // List of spatial entities contained in the entity to which this component is attached. + ovrpUuid* uuids; +} ovrpSpaceContainer; + +typedef struct ovrpSemanticLabels_ { + // Input, capacity of the label buffer in byte. + int byteCapacityInput; + // Output, size of the label buffer in byte. + int byteCountOutput; + // Multiple labels represented by raw string, separated by comma (,). + char* labels; +} ovrpSemanticLabels; + +typedef struct ovrpRoomLayout_ { + // UUID, floor of the room layout. + ovrpUuid floorUuid; + // UUID, ceiling of the room layout. + ovrpUuid ceilingUuid; + // Input, indicating the capacity of pointer `wallUuids`. + int wallUuidCapacityInput; + // Output, number of walls included in the list. + int wallUuidCountOutput; + // Ordered list of walls of the room layout. + ovrpUuid* wallUuids; +} ovrpRoomLayout; + +typedef struct ovrpBoundary2D_ { + // Input, capacity of the vertex buffer. + int vertexCapacityInput; + // Output, size of the vertex buffer. + int vertexCountOutput; + // Vertices of the polygonal boundary in the coordinate frame of the associated space. + // Currently only support outer bounds. + ovrpVector2f* vertices; +} ovrpBoundary2D; + +typedef struct ovrpEventSceneCaptureComplete_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpResult result; +} ovrpEventSceneCaptureComplete; + +#define OVRP_SCENE_CAPTURE_MAX_REQUEST_TYPE_COUNT 30 + +typedef struct ovrpSceneCaptureRequest_ { + int requestByteCount; + char* request; +} ovrpSceneCaptureRequest; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +typedef enum { + ovrpInteractionProfile_None = 0, + ovrpInteractionProfile_Touch = 1, + ovrpInteractionProfile_TouchPro = 2, + + + + ovrpInteractionProfile_EnumSize = 0x7fffffff +} ovrpInteractionProfile; + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif // __clang__ + +#endif diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Types_Deprecated.h b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Types_Deprecated.h new file mode 100644 index 0000000000000000000000000000000000000000..fd70ada5ec5fbaaf617052df32389ae66c746dfa --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Include/OVR_Plugin_Types_Deprecated.h @@ -0,0 +1,235 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * All rights reserved. + * + * Licensed under the Oculus SDK License Agreement (the "License"); + * you may not use the Oculus SDK except in compliance with the License, + * which is provided at the time of installation or download, or which + * otherwise accompanies this software in either electronic or hard copy form. + * + * You may obtain a copy of the License at + * + * https://developer.oculus.com/licenses/oculussdk/ + * + * Unless required by applicable law or agreed to in writing, the Oculus SDK + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OVR_Plugin_Types_Deprecated_h +#define OVR_Plugin_Types_Deprecated_h + +#include "OVR_Plugin_Types.h" + +#if defined ANDROID || defined __linux__ +#define __cdecl +#endif + +/// Symmetric frustum for a camera. +typedef struct { + /// Near clip plane. + float zNear; + /// Far clip plane. + float zFar; + /// Horizontal field of view. + float fovX; + /// Vertical field of view. + float fovY; +} ovrpFrustumf; + +const static ovrpFrustumf s_identityFrustum = {0, 0, 0, 0}; + +/// Describes Input State for use with Gamepads and Oculus Controllers. +typedef struct { + unsigned int ConnectedControllerTypes; + unsigned int Buttons; + unsigned int Touches; + unsigned int NearTouches; + float IndexTrigger[2]; + float HandTrigger[2]; + ovrpVector2f Thumbstick[2]; + ovrpVector2f Touchpad[2]; +} ovrpControllerState2; + +/// Describes Input State for use with Gamepads and Oculus Controllers. +typedef struct { + unsigned int ConnectedControllerTypes; + unsigned int Buttons; + unsigned int Touches; + unsigned int NearTouches; + float IndexTrigger[2]; + float HandTrigger[2]; + ovrpVector2f Thumbstick[2]; +} ovrpControllerState; + +typedef ovrpControllerState ovrpInputState; + +typedef struct ovrpSkeleton_ { + ovrpSkeletonType SkeletonType; + unsigned int NumBones; + unsigned int NumBoneCapsules; + ovrpBone Bones[ovrpSkeletonConstants_MaxHandBones]; + ovrpBoneCapsule BoneCapsules[ovrpSkeletonConstants_MaxBoneCapsules]; +} ovrpSkeleton; + +// Old UUID type +typedef struct ovrpSpatialEntityUuid { + // unique id value + ovrpUInt64 value[OVRP_SPATIAL_ENTITY_UUID_SIZE]; +} ovrpSpatialEntityUuid; + +// Backward compatibility for Unreal integration +typedef ovrpSpatialAnchorCreateInfo ovrpSpaceAnchorCreateInfo; + +// Single Space Load is no longer supported +typedef struct ovrpEventSpaceStorageLoadResult_ { + ovrpEventType EventType; + ovrpUInt64 requestId; + ovrpSpace space; + ovrpResult result; + ovrpSpatialEntityUuid uuid; +} ovrpEventSpaceStorageLoadResult; + +/// Capability bits that control the plugin's configuration. +/// Each value corresponds to a left-shift offset in the bitfield. +typedef enum { + /// If true, sRGB read-write occurs, reducing eye texture aliasing. + ovrpCap_SRGB = 0, + /// If true, the image will be corrected for chromatic aberration. + ovrpCap_Chromatic, + /// If true, eye textures are flipped on the Y axis before display. + ovrpCap_FlipInput, + /// If true, head tracking affects the rotation reported by ovrp_GetEyePose. + ovrpCap_Rotation, + /// (Deprecated) If true, head rotation affects the position reported by ovrp_GetEyePose. + ovrpCap_HeadModel, + /// If true, head position tracking affects the poses returned by ovrp_GetEyePose. + ovrpCap_Position, + /// If true, the runtime collects performance statistics for debugging. + ovrpCap_CollectPerf, + /// If true, a debugging heads-up display appears in the scene. + ovrpCap_DebugDisplay, + /// If true, the left eye image is shown to both eyes. Right is ignored. + ovrpCap_Monoscopic, + /// If true, both eyes share texture 0, with the left eye on the left side. + ovrpCap_ShareTexture, + /// If true, a clip mesh will be provided for both eyes + ovrpCap_OcclusionMesh, + ovrpCap_EnumSize = 0x7fffffff +} ovrpCaps; + +/// Read-only bits that reflect the plugins' current status. +/// Each value corresponds to a left-shift offset in the bitfield. +typedef enum { + /// If true, the VR display is virtual and no physical device is attached. + ovrpStatus_Debug = 0, + /// (Deprecated) If true, the health & safety warning is currently visible. + ovrpStatus_HSWVisible, + /// If true, the HMD supports position tracking (e.g. a camera is attached). + ovrpStatus_PositionSupported, + /// If true, position tracking is active and not obstructed. + ovrpStatus_PositionTracked, + /// If true, the system has reduced performance to save power. + ovrpStatus_PowerSaving, + /// If true, the plugin is initialized and ready for use. + ovrpStatus_Initialized, + /// If true, a working VR display is present, but it may be a "debug" display. + ovrpStatus_HMDPresent, + /// If true, the user is currently wearing the VR display and it is not idle. + ovrpStatus_UserPresent, + /// If true, the app has VR focus. + ovrpStatus_HasVrFocus, + /// If true, the app should quit as soon as possible. + ovrpStatus_ShouldQuit, + /// If true, the app should call ovrp_RecenterPose as soon as possible. + ovrpStatus_ShouldRecenter, + /// If true, we need to recreate the session + ovrpStatus_ShouldRecreateDistortionWindow, + ovrpStatus_EnumSize = 0x7fffffff +} ovrpStatus; + +typedef enum { + /// (String) Identifies the version of OVRPlugin you are using. Format: "major.minor.release" + ovrpKey_Version, + /// (String) Identifies the type of VR display device in use, if any. + ovrpKey_ProductName, + /// (String) The latest measured latency. + ovrpKey_Latency, + /// (Float) The physical distance from the front of the player's eye to the back of their neck + /// in meters. + ovrpKey_EyeDepth, + /// (Float) The physical height of the player's eyes from the ground in meters. + ovrpKey_EyeHeight, + /// (Float, read-only) The current available battery charge, ranging from 0 (empty) to 1 (full). + ovrpKey_BatteryLevel, + /// (Float, read-only) The current battery temperature in degrees Celsius. + ovrpKey_BatteryTemperature, + /// (Float) The current CPU performance level, rounded down to nearest integer in the range 0-2. + ovrpKey_CpuLevel, + /// (Float) The current GPU performance level, rounded down to nearest integer in the range 0-2. + ovrpKey_GpuLevel, + /// (Float, read-only) The current system volume level. + ovrpKey_SystemVolume, + /// (Float) The fraction of a frame ahead to predict poses and allow GPU-CPU parallelism. + /// Trades latency for performance. + ovrpKey_QueueAheadFraction, + /// (Float) The physical inter-pupillary distance (IPD) separating the user's eyes in meters. + ovrpKey_IPD, + /// (Float) The number of allocated eye texture texels per screen pixel in each direction + /// (horizontal and vertical). + ovrpKey_NativeTextureScale, + /// (Float) The number of rendered eye texture texels per screen pixel based on viewport scaling. + ovrpKey_VirtualTextureScale, + /// (Float) The native refresh rate of the HMD. + ovrpKey_Frequency, + /// (String) The version of the underlying SDK in use. + ovrpKey_SDKVersion, + ovrpKey_EnumSize = 0x7fffffff +} ovrpKey; + +typedef ovrpShape ovrpOverlayShape; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wpedantic" +#endif // __clang__ + +typedef enum { + ovrpOverlayFlag_None = 0x00000000, + /// If true, the overlay appears on top of all lower-indexed layers and the eye buffers. + ovrpOverlayFlag_OnTop = 0x00000001, + /// If true, the overlay bypasses TimeWarp and directly follows head motion. + ovrpOverlayFlag_HeadLocked = 0x00000002, + /// If true, the overlay will not allow depth compositing on Rift. + ovrpOverlayFlag_NoDepth = 0x00000004, + // If true, the overlay will use the VrApi supersample flag, which can be helpful but is extremely expensive. + ovrpOverlayFlag_ExpensiveSuperSample = 0x00000008, + // if true, the overlay will use the Vrapi efficient supersample flag + ovrpOverlayFlag_EfficientSuperSample = 0x00000010, + // If true, the overlay will use the Vrapi sharpen flag + ovrpOverlayFlag_EfficientSharpen = 0x00000020, + // If true, the overlay will use bicubic filtering flag + ovrpOverlayFlag_BicubicFiltering = 0x00000040, + // If true, the overlay will use the Vrapi sharpen flag + ovrpOverlayFlag_QualitySharpen = 0x00000080, + // If true, the overlay will be "secure content"; the contents cannot be recorded by users + ovrpOverlayFlag_SecureContent = 0x00000100, + + ovrpOverlayFlag_Hidden = 0x00000200, + + // Internal flags + /// If true, the overlay is a loading screen. + ovrpOverlayFlag_LoadingScreen = 0x40000000, + /// If true, the overlay bypasses distortion and is copied directly to the display + /// (possibly with scaling). + ovrpOverlayFlag_Undistorted = 0x80000000, + ovrpOverlayFlag_EnumSize = 0x7fffffff +} ovrpOverlayFlag; + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif // __clang__ + +#endif diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win32/OVRPlugin.dll b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win32/OVRPlugin.dll new file mode 100644 index 0000000000000000000000000000000000000000..d48d6c083bd80572a3b15432aa6c4287cc77fb01 Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win32/OVRPlugin.dll differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win32/OVRPlugin.lib b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win32/OVRPlugin.lib new file mode 100644 index 0000000000000000000000000000000000000000..b43f1f224f84c31547988af0a40110a237e1159c Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win32/OVRPlugin.lib differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OVRPlugin.dll b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OVRPlugin.dll new file mode 100644 index 0000000000000000000000000000000000000000..f4cd30d97c0e8191fd0c84f15ba4beab985bbade Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OVRPlugin.dll differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OVRPlugin.lib b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OVRPlugin.lib new file mode 100644 index 0000000000000000000000000000000000000000..c2342887059691bf92e33c9a0d73100d25c4d091 Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OVRPlugin.lib differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OpenXR/OVRPlugin.dll b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OpenXR/OVRPlugin.dll new file mode 100644 index 0000000000000000000000000000000000000000..c29ef26f9cfd8adfc95fc30c5d065d6a4f836ed1 Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OpenXR/OVRPlugin.dll differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OpenXR/OVRPlugin.lib b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OpenXR/OVRPlugin.lib new file mode 100644 index 0000000000000000000000000000000000000000..9060508332213dc0872b03a88d21f837ae482cf4 Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/Win64/OpenXR/OVRPlugin.lib differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/arm64-v8a/OpenXR/libOVRPlugin.so b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/arm64-v8a/OpenXR/libOVRPlugin.so new file mode 100644 index 0000000000000000000000000000000000000000..698a79be0fe5fc87c72a9239c6f56506c628688d Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/arm64-v8a/OpenXR/libOVRPlugin.so differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/arm64-v8a/libOVRPlugin.so b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/arm64-v8a/libOVRPlugin.so new file mode 100644 index 0000000000000000000000000000000000000000..ca264ae14bb423b669697c1f1cf6482c11b15e37 Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/arm64-v8a/libOVRPlugin.so differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/armeabi-v7a/OpenXR/libOVRPlugin.so b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/armeabi-v7a/OpenXR/libOVRPlugin.so new file mode 100644 index 0000000000000000000000000000000000000000..89a1a84698c0bae9e320fcbcb2c0a0561eb09971 Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/armeabi-v7a/OpenXR/libOVRPlugin.so differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/armeabi-v7a/libOVRPlugin.so b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/armeabi-v7a/libOVRPlugin.so new file mode 100644 index 0000000000000000000000000000000000000000..a7255545f5cd95749d857db3d98e848f180165eb Binary files /dev/null and b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPlugin/Lib/armeabi-v7a/libOVRPlugin.so differ diff --git a/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPluginXR.build.cs b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPluginXR.build.cs new file mode 100644 index 0000000000000000000000000000000000000000..45186a94e53f8ebc1f665271859de448c02f4926 --- /dev/null +++ b/Plugins/OculusXR/Source/Thirdparty/OVRPlugin/OVRPluginXR.build.cs @@ -0,0 +1,21 @@ +// Copyright Epic Games, Inc. All Rights Reserved. + +using UnrealBuildTool; + +public class OVRPluginXR : ModuleRules +{ + public OVRPluginXR(ReadOnlyTargetRules Target) : base(Target) + { + Type = ModuleType.External; + + string SourceDirectory = "$(PluginDir)/Source/ThirdParty/OVRPlugin/OVRPlugin/"; + + PublicIncludePaths.Add(SourceDirectory + "Include"); + + if (Target.Platform == UnrealTargetPlatform.Android) + { + RuntimeDependencies.Add(SourceDirectory + "Lib/armeabi-v7a/OpenXR/libOVRPlugin.so"); + RuntimeDependencies.Add(SourceDirectory + "Lib/arm64-v8a/OpenXR/libOVRPlugin.so"); + } + } +} diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserApp.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserApp.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d4a980bf4999074008360fa608e2b0f5d974acbe --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserApp.cpp @@ -0,0 +1,119 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserApp.cpp + +#include "CEF/CEFInterfaceBrowserApp.h" +#include "HAL/IConsoleManager.h" + +#if WITH_CEF3 +#include "WebInterfaceBrowserLog.h" + +//#define DEBUG_CEFMESSAGELOOP_FRAMERATE 1 // uncomment this to have debug spew about the FPS we call the CefDoMessageLoopWork function + +DEFINE_LOG_CATEGORY(LogCEFInterfaceBrowser); +/* +static bool bCEFGPUAcceleration = true; +static FAutoConsoleVariableRef CVarCEFGPUAcceleration( + TEXT("r.CEFGPUAcceleration"), + bCEFGPUAcceleration, + TEXT("Enables GPU acceleration in CEF\n"), + ECVF_Default); +*/ +FCEFInterfaceBrowserApp::FCEFInterfaceBrowserApp(bool bInGPU) + : MessagePumpCountdown(0) + , bGPU(bInGPU) +{ +} + +void FCEFInterfaceBrowserApp::OnBeforeChildProcessLaunch(CefRefPtr<CefCommandLine> CommandLine) +{ +} + +void FCEFInterfaceBrowserApp::OnBeforeCommandLineProcessing(const CefString& ProcessType, CefRefPtr< CefCommandLine > CommandLine) +{ + if (bGPU) + { + UE_LOG(LogCEFInterfaceBrowser, Log, TEXT("CEF GPU acceleration enabled")); + CommandLine->AppendSwitch("enable-gpu"); + CommandLine->AppendSwitch("enable-gpu-compositing"); + } + else + { + UE_LOG(LogCEFInterfaceBrowser, Log, TEXT("CEF GPU acceleration disabled")); + CommandLine->AppendSwitch("disable-gpu"); + CommandLine->AppendSwitch("disable-gpu-compositing"); + } + +#if PLATFORM_LINUX + CommandLine->AppendSwitchWithValue("ozone-platform", "headless"); + CommandLine->AppendSwitchWithValue("use-gl", "angle"); + CommandLine->AppendSwitchWithValue("use-angle", "vulkan"); + CommandLine->AppendSwitch("use-vulkan"); +#endif + + CommandLine->AppendSwitch("enable-begin-frame-scheduling"); + CommandLine->AppendSwitch("disable-pinch"); // the web pages we have don't expect zoom to work right now so disable touchpad pinch zoom + CommandLine->AppendSwitch("disable-gpu-shader-disk-cache"); // Don't create a "GPUCache" directory when cache-path is unspecified. +#if PLATFORM_MAC + CommandLine->AppendSwitch("use-mock-keychain"); // Disable the toolchain prompt on macOS. +#endif +} + +void FCEFInterfaceBrowserApp::OnScheduleMessagePumpWork(int64 delay_ms) +{ + FScopeLock Lock(&MessagePumpCountdownCS); + + // As per CEF documentation, if delay_ms is <= 0, then the call to CefDoMessageLoopWork should happen reasonably soon. If delay_ms is > 0, then the call + // to CefDoMessageLoopWork should be scheduled to happen after the specified delay and any currently pending scheduled call should be canceled. + if(delay_ms < 0) + { + delay_ms = 0; + } + MessagePumpCountdown = delay_ms; +} + +bool FCEFInterfaceBrowserApp::TickMessagePump(float DeltaTime, bool bForce) +{ + bool bPump = false; + { + FScopeLock Lock(&MessagePumpCountdownCS); + + // count down in order to call message pump + if (MessagePumpCountdown >= 0) + { + MessagePumpCountdown -= (DeltaTime * 1000); + if (MessagePumpCountdown <= 0) + { + bPump = true; + } + + if (bPump || bForce) + { + // -1 indicates that no countdown is currently happening + MessagePumpCountdown = -1; + } + } + } + +#ifdef DEBUG_CEFMESSAGELOOP_FRAMERATE + static float SecondsFrameRate = 0; + static int NumFrames = 0; + SecondsFrameRate += DeltaTime; +#endif + if (bPump || bForce) + { +#ifdef DEBUG_CEFMESSAGELOOP_FRAMERATE + ++NumFrames; + if (NumFrames % 100 == 0 || SecondsFrameRate > 5.0f) + { + UE_LOG(LogWebBrowser, Error, TEXT("CefDoMessageLoopWork call Frame Rate %0.2f"), NumFrames / SecondsFrameRate); + SecondsFrameRate = 0; + NumFrames = 0; + } +#endif + + CefDoMessageLoopWork(); + return true; + } + return false; +} + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserApp.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserApp.h new file mode 100644 index 0000000000000000000000000000000000000000..dd251f5a3bc220894e01dcd88f65e16ab2588d03 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserApp.h @@ -0,0 +1,48 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserApp.h + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/ScopeLock.h" + +#if WITH_CEF3 + +#include "CEFInterfaceLibCefIncludes.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogCEFInterfaceBrowser, Log, All); + +/** + * Implements CEF App and other Process level interfaces + */ +class FCEFInterfaceBrowserApp : public CefApp, + public CefBrowserProcessHandler +{ +public: + + /** + * Default Constructor + */ + FCEFInterfaceBrowserApp(bool bInGPU); + + /** Used to pump the CEF message loop whenever OnScheduleMessagePumpWork is triggered */ + bool TickMessagePump(float DeltaTime, bool bForce); + +private: + // CefApp methods. + virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() override { return this; } + virtual void OnBeforeCommandLineProcessing(const CefString& ProcessType, CefRefPtr< CefCommandLine > CommandLine) override; + // CefBrowserProcessHandler methods: + virtual void OnBeforeChildProcessLaunch(CefRefPtr<CefCommandLine> CommandLine) override; + virtual void OnScheduleMessagePumpWork(int64 delay_ms) override; + + // Include the default reference counting implementation. + IMPLEMENT_REFCOUNTING(FCEFInterfaceBrowserApp); + + // Lock for access MessagePumpCountdown + FCriticalSection MessagePumpCountdownCS; + // Countdown in milliseconds until CefDoMessageLoopWork is called. Updated by OnScheduleMessagePumpWork + int64 MessagePumpCountdown; + // WebGL toggle + bool bGPU; +}; +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserByteResource.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserByteResource.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b39ae12b5a5358a242703b98ce5e491f287be543 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserByteResource.cpp @@ -0,0 +1,56 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserByteResource.cpp + +#include "CEF/CEFInterfaceBrowserByteResource.h" + +#if WITH_CEF3 + +FCEFInterfaceBrowserByteResource::FCEFInterfaceBrowserByteResource(const CefRefPtr<CefPostDataElement>& PostData, const FString& InMimeType) + : Position(0) + , Buffer(nullptr) + , MimeType(InMimeType) +{ + Size = PostData->GetBytesCount(); + if (Size > 0) + { + Buffer = new unsigned char[Size]; + PostData->GetBytes(Size, Buffer); + } +} + +FCEFInterfaceBrowserByteResource::~FCEFInterfaceBrowserByteResource() +{ + delete[] Buffer; +} + +void FCEFInterfaceBrowserByteResource::Cancel() +{ + +} + +void FCEFInterfaceBrowserByteResource::GetResponseHeaders(CefRefPtr<CefResponse> Response, int64& ResponseLength, CefString& RedirectUrl) +{ + Response->SetMimeType(TCHAR_TO_WCHAR(*MimeType)); + Response->SetStatus(200); + Response->SetStatusText("OK"); + ResponseLength = Size; +} + +bool FCEFInterfaceBrowserByteResource::ProcessRequest(CefRefPtr<CefRequest> Request, CefRefPtr<CefCallback> Callback) +{ + Callback->Continue(); + return true; +} + +bool FCEFInterfaceBrowserByteResource::ReadResponse(void* DataOut, int BytesToRead, int& BytesRead, CefRefPtr<CefCallback> Callback) +{ + int32 BytesLeft = Size - Position; + BytesRead = BytesLeft >= BytesToRead ? BytesToRead : BytesLeft; + if (BytesRead > 0) + { + FMemory::Memcpy(DataOut, Buffer + Position, BytesRead); + Position += BytesRead; + return true; + } + return false; +} +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserByteResource.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserByteResource.h new file mode 100644 index 0000000000000000000000000000000000000000..e60bf798c74bf3e23f736bb07e02c74c82347a3e --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserByteResource.h @@ -0,0 +1,41 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserByteResource.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 + +#include "CEFInterfaceLibCefIncludes.h" + + +/** + * Implements a resource handler that will return the contents of a string as the result. + */ +class FCEFInterfaceBrowserByteResource + : public CefResourceHandler +{ +public: + /** + */ + FCEFInterfaceBrowserByteResource(const CefRefPtr<CefPostDataElement>& PostData, const FString& InMimeType); + ~FCEFInterfaceBrowserByteResource(); + + // CefResourceHandler interface + virtual void Cancel() override; + virtual void GetResponseHeaders(CefRefPtr<CefResponse> Response, int64& ResponseLength, CefString& RedirectUrl) override; + virtual bool ProcessRequest(CefRefPtr<CefRequest> Request, CefRefPtr<CefCallback> Callback) override; + virtual bool ReadResponse(void* DataOut, int BytesToRead, int& BytesRead, CefRefPtr<CefCallback> Callback) override; + +private: + int32 Position; + int32 Size; + unsigned char* Buffer; + FString MimeType; + + // Include the default reference counting implementation. + IMPLEMENT_REFCOUNTING(FCEFInterfaceBrowserByteResource); +}; + + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserClosureTask.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserClosureTask.h new file mode 100644 index 0000000000000000000000000000000000000000..db4a3069210c8249fa9f84d18f12cf461d05b82e --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserClosureTask.h @@ -0,0 +1,32 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserClosureTask.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 +#include "CEFInterfaceLibCefIncludes.h" + +// Helper for posting a closure as a task +class FCEFInterfaceBrowserClosureTask + : public CefTask +{ +public: + FCEFInterfaceBrowserClosureTask(CefRefPtr<CefBaseRefCounted> InHandle, TFunction<void ()> InClosure) + : Handle(InHandle) + , Closure(InClosure) + { } + + virtual void Execute() override + { + Closure(); + } + +private: + CefRefPtr<CefBaseRefCounted> Handle; // Used so the handler will not go out of scope before the closure is executed. + TFunction<void ()> Closure; + IMPLEMENT_REFCOUNTING(FCEFInterfaceBrowserClosureTask); +}; + + +#endif /* WITH_CEF3 */ diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserHandler.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9b622bb7d0570dc0f312f859fd13f0f9d968713b --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserHandler.cpp @@ -0,0 +1,917 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserHandler.cpp + +#include "CEF/CEFInterfaceBrowserHandler.h" +#include "HAL/PlatformApplicationMisc.h" + +#if WITH_CEF3 + +//#define DEBUG_ONBEFORELOAD // Debug print beforebrowse steps + +#include "WebInterfaceBrowserModule.h" +#include "CEFInterfaceBrowserClosureTask.h" +#include "IWebInterfaceBrowserSingleton.h" +#include "WebInterfaceBrowserSingleton.h" +#include "CEFInterfaceBrowserPopupFeatures.h" +#include "CEFWebInterfaceBrowserWindow.h" +#include "CEFInterfaceBrowserByteResource.h" +#include "Framework/Application/SlateApplication.h" +#include "HAL/ThreadingBase.h" +#include "PlatformHttp.h" +#include "Misc/CommandLine.h" + +#define LOCTEXT_NAMESPACE "WebInterfaceBrowserHandler" + +#ifdef DEBUG_ONBEFORELOAD +// Debug helper function to track URL loads +void LogCEFLoad(const FString &Msg, CefRefPtr<CefRequest> Request) +{ + auto url = Request->GetURL(); + auto type = Request->GetResourceType(); + if (type == CefRequest::ResourceType::RT_MAIN_FRAME || type == CefRequest::ResourceType::RT_XHR || type == CefRequest::ResourceType::RT_SUB_RESOURCE|| type == CefRequest::ResourceType::RT_SUB_FRAME) + { + GLog->Logf(ELogVerbosity::Display, TEXT("%s :%s type:%s"), *Msg, url.c_str(), *ResourceTypeToString(type)); + } +} + +#define LOG_CEF_LOAD(MSG) LogCEFLoad(#MSG, Request) +#else +#define LOG_CEF_LOAD(MSG) +#endif + +// Used to force returning custom content instead of performing a request. +const FString CustomContentMethod(TEXT("X-GET-CUSTOM-CONTENT")); + +FCEFInterfaceBrowserHandler::FCEFInterfaceBrowserHandler(bool InUseTransparency, bool InInterceptLoadRequests, const TArray<FString>& InAltRetryDomains, const TArray<FString>& InAuthorizationHeaderAllowListURLS) +: bUseTransparency(InUseTransparency), +bAllowAllCookies(false), +bInterceptLoadRequests(InInterceptLoadRequests), +AltRetryDomains(InAltRetryDomains), +AuthorizationHeaderAllowListURLS(InAuthorizationHeaderAllowListURLS) +{ + // should we forcefully allow all cookies to be set rather than filtering a couple store side ones + bAllowAllCookies = FParse::Param(FCommandLine::Get(), TEXT("CefAllowAllCookies")); +} + +void FCEFInterfaceBrowserHandler::OnTitleChange(CefRefPtr<CefBrowser> Browser, const CefString& Title) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + BrowserWindow->SetTitle(Title); + } +} + +void FCEFInterfaceBrowserHandler::OnAddressChange(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, const CefString& Url) +{ + if (Frame->IsMain()) + { + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + BrowserWindow->SetUrl(Url); + } + } +} + +bool FCEFInterfaceBrowserHandler::OnTooltip(CefRefPtr<CefBrowser> Browser, CefString& Text) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + BrowserWindow->SetToolTip(Text); + } + + return false; +} + +bool FCEFInterfaceBrowserHandler::OnConsoleMessage(CefRefPtr<CefBrowser> Browser, cef_log_severity_t level, const CefString& Message, const CefString& Source, int Line) +{ + ConsoleMessageDelegate.ExecuteIfBound(Browser, level, Message, Source, Line); + // Return false to let it output to console. + return false; +} + +void FCEFInterfaceBrowserHandler::OnAfterCreated(CefRefPtr<CefBrowser> Browser) +{ + if(Browser->IsPopup()) + { + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindowParent = ParentHandler.get() ? ParentHandler->BrowserWindowPtr.Pin() : nullptr; + if(BrowserWindowParent.IsValid() && ParentHandler->OnCreateWindow().IsBound()) + { + TSharedPtr<FWebInterfaceBrowserWindowInfo> NewBrowserWindowInfo = MakeShareable(new FWebInterfaceBrowserWindowInfo(Browser, this)); + TSharedPtr<IWebInterfaceBrowserWindow> NewBrowserWindow = IWebInterfaceBrowserModule::Get().GetSingleton()->CreateBrowserWindow( + BrowserWindowParent, + NewBrowserWindowInfo + ); + + { + // @todo: At the moment we need to downcast since the handler does not support using the interface. + TSharedPtr<FCEFWebInterfaceBrowserWindow> HandlerSpecificBrowserWindow = StaticCastSharedPtr<FCEFWebInterfaceBrowserWindow>(NewBrowserWindow); + BrowserWindowPtr = HandlerSpecificBrowserWindow; + } + + // Request a UI window for the browser. If it is not created we do some cleanup. + bool bUIWindowCreated = ParentHandler->OnCreateWindow().Execute(TWeakPtr<IWebInterfaceBrowserWindow>(NewBrowserWindow), TWeakPtr<IWebInterfaceBrowserPopupFeatures>(BrowserPopupFeatures)); + if(!bUIWindowCreated) + { + NewBrowserWindow->CloseBrowser(true); + } + else + { + checkf(!NewBrowserWindow.IsUnique(), TEXT("Handler indicated that new window UI was created, but failed to save the new WebBrowserWindow instance.")); + } + } + else + { + Browser->GetHost()->CloseBrowser(true); + } + } +} + +bool FCEFInterfaceBrowserHandler::DoClose(CefRefPtr<CefBrowser> Browser) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if(BrowserWindow.IsValid()) + { + BrowserWindow->OnBrowserClosing(); + } +#if PLATFORM_WINDOWS + // If we have a window handle, we're rendering directly to the screen and not off-screen + HWND NativeWindowHandle = Browser->GetHost()->GetWindowHandle(); + if (NativeWindowHandle != nullptr) + { + HWND ParentWindow = ::GetParent(NativeWindowHandle); + + if (ParentWindow) + { + HWND FocusHandle = ::GetFocus(); + if (FocusHandle && (FocusHandle == NativeWindowHandle || ::IsChild(NativeWindowHandle, FocusHandle))) + { + // Set focus to the parent window, otherwise keyboard and mouse wheel input will become wonky + ::SetFocus(ParentWindow); + } + // CEF will send a WM_CLOSE to the parent window and potentially exit the application if we don't do this + ::SetParent(NativeWindowHandle, nullptr); + } + } +#endif + return false; +} + +void FCEFInterfaceBrowserHandler::OnBeforeClose(CefRefPtr<CefBrowser> Browser) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + BrowserWindow->OnBrowserClosed(); + } + +} + +bool FCEFInterfaceBrowserHandler::OnBeforePopup( CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + const CefString& TargetUrl, + const CefString& TargetFrameName, + const CefPopupFeatures& PopupFeatures, + CefWindowInfo& OutWindowInfo, + CefRefPtr<CefClient>& OutClient, + CefBrowserSettings& OutSettings, + bool* OutNoJavascriptAccess ) +{ + FString URL = WCHAR_TO_TCHAR(TargetUrl.ToWString().c_str()); + FString FrameName = WCHAR_TO_TCHAR(TargetFrameName.ToWString().c_str()); + + /* If OnBeforePopup() is not bound, we allow creating new windows as long as OnCreateWindow() is bound. + The BeforePopup delegate is always executed even if OnCreateWindow is not bound to anything . + */ + if((OnBeforePopup().IsBound() && OnBeforePopup().Execute(URL, FrameName)) || !OnCreateWindow().IsBound()) + { + return true; + } + else + { + TSharedPtr<FCEFInterfaceBrowserPopupFeatures> NewBrowserPopupFeatures = MakeShareable(new FCEFInterfaceBrowserPopupFeatures(PopupFeatures)); + bool bIsDevtools = URL.Contains(TEXT("chrome-devtools")); + bool shouldUseTransparency = bIsDevtools ? false : bUseTransparency; + NewBrowserPopupFeatures->SetResizable(bIsDevtools); // only have the window for DevTools have resize options + + cef_color_t Alpha = shouldUseTransparency ? 0 : CefColorGetA(OutSettings.background_color); + cef_color_t R = CefColorGetR(OutSettings.background_color); + cef_color_t G = CefColorGetG(OutSettings.background_color); + cef_color_t B = CefColorGetB(OutSettings.background_color); + OutSettings.background_color = CefColorSetARGB(Alpha, R, G, B); + + CefRefPtr<FCEFInterfaceBrowserHandler> NewHandler(new FCEFInterfaceBrowserHandler(shouldUseTransparency, true /*InterceptLoadRequests*/)); + NewHandler->ParentHandler = this; + NewHandler->SetPopupFeatures(NewBrowserPopupFeatures); + OutClient = NewHandler; + + // Always use off screen rendering so we can integrate with our windows +#if PLATFORM_LINUX + OutWindowInfo.SetAsWindowless(kNullWindowHandle); +#elif PLATFORM_WINDOWS + OutWindowInfo.SetAsWindowless(kNullWindowHandle); + OutWindowInfo.shared_texture_enabled = 0; // always render popups with the simple OSR renderer +#elif PLATFORM_MAC + OutWindowInfo.SetAsWindowless(kNullWindowHandle); + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + OutWindowInfo.shared_texture_enabled = BrowserWindow->UsingAcceleratedPaint() ? 1 : 0; // match what other windows do + } + else + { + OutWindowInfo.shared_texture_enabled = 0; + } +#else + OutWindowInfo.SetAsWindowless(kNullWindowHandle); +#endif + + // We need to rely on CEF to create our window so we set the WindowInfo, BrowserSettings, Client, and then return false + return false; + } +} + +bool FCEFInterfaceBrowserHandler::OnCertificateError(CefRefPtr<CefBrowser> Browser, + cef_errorcode_t CertError, + const CefString &RequestUrl, + CefRefPtr<CefSSLInfo> SslInfo, + CefRefPtr<CefRequestCallback> Callback) +{ + // Forward the cert error to the normal load error handler + CefString ErrorText = "Certificate error"; + OnLoadError(Browser, Browser->GetMainFrame(), CertError, ErrorText, RequestUrl); + return false; +} + +void FCEFInterfaceBrowserHandler::OnLoadError(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefLoadHandler::ErrorCode InErrorCode, + const CefString& ErrorText, + const CefString& FailedUrl) +{ + + // notify browser window + if (Frame->IsMain()) + { + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + if (AltRetryDomains.Num() > 0 && AltRetryDomainIdx < (uint32)AltRetryDomains.Num()) + { + FString Url = WCHAR_TO_TCHAR(FailedUrl.ToWString().c_str()); + FString OriginalUrlDomain = FPlatformHttp::GetUrlDomain(Url); + if (!OriginalUrlDomain.IsEmpty()) + { + const FString NewUrl(Url.Replace(*OriginalUrlDomain, *AltRetryDomains[AltRetryDomainIdx++])); + BrowserWindow->LoadURL(NewUrl); + return; + } + + } + BrowserWindow->NotifyDocumentError(InErrorCode, ErrorText, FailedUrl); + } + } +} + +void FCEFInterfaceBrowserHandler::OnLoadStart(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, TransitionType CefTransitionType) +{ +} + +void FCEFInterfaceBrowserHandler::OnLoadingStateChange(CefRefPtr<CefBrowser> Browser, bool bIsLoading, bool bCanGoBack, bool bCanGoForward) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + BrowserWindow->NotifyDocumentLoadingStateChange(bIsLoading); + } +} + +bool FCEFInterfaceBrowserHandler::GetRootScreenRect(CefRefPtr<CefBrowser> Browser, CefRect& Rect) +{ + if (CefCurrentlyOn(TID_UI)) + { + // CEF may call this off the main gamethread which slate requires, so double check here + FDisplayMetrics DisplayMetrics; + FSlateApplication::Get().GetDisplayMetrics(DisplayMetrics); + Rect.width = DisplayMetrics.PrimaryDisplayWidth; + Rect.height = DisplayMetrics.PrimaryDisplayHeight; + return true; + } + + return false; +} + +void FCEFInterfaceBrowserHandler::GetViewRect(CefRefPtr<CefBrowser> Browser, CefRect& Rect) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + BrowserWindow->GetViewRect(Rect); + } + else + { + // CEF requires at least a 1x1 area for painting + Rect.x = Rect.y = 0; + Rect.width = Rect.height = 1; + } +} + +void FCEFInterfaceBrowserHandler::OnPaint(CefRefPtr<CefBrowser> Browser, + PaintElementType Type, + const RectList& DirtyRects, + const void* Buffer, + int Width, int Height) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + BrowserWindow->OnPaint(Type, DirtyRects, Buffer, Width, Height); + } +} + +void FCEFInterfaceBrowserHandler::OnAcceleratedPaint(CefRefPtr<CefBrowser> Browser, + PaintElementType Type, + const RectList& DirtyRects, + void* SharedHandle) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + BrowserWindow->OnAcceleratedPaint(Type, DirtyRects, SharedHandle); + } +} + +bool FCEFInterfaceBrowserHandler::OnCursorChange(CefRefPtr<CefBrowser> Browser, CefCursorHandle Cursor, cef_cursor_type_t Type, const CefCursorInfo& CustomCursorInfo) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + return BrowserWindow->OnCursorChange(Cursor, Type, CustomCursorInfo); + } + return false; +} + +void FCEFInterfaceBrowserHandler::OnPopupShow(CefRefPtr<CefBrowser> Browser, bool bShow) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + BrowserWindow->ShowPopupMenu(bShow); + } + +} + +void FCEFInterfaceBrowserHandler::OnPopupSize(CefRefPtr<CefBrowser> Browser, const CefRect& Rect) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + BrowserWindow->SetPopupMenuPosition(Rect); + } +} + +bool FCEFInterfaceBrowserHandler::GetScreenInfo(CefRefPtr<CefBrowser> Browser, CefScreenInfo& ScreenInfo) +{ + TSharedPtr<FWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + ScreenInfo.depth = 24; + + if (BrowserWindow.IsValid() && BrowserWindow->GetParentWindow().IsValid()) + { + ScreenInfo.device_scale_factor = BrowserWindow->GetParentWindow()->GetNativeWindow()->GetDPIScaleFactor(); + } + else + { + FDisplayMetrics DisplayMetrics; + FDisplayMetrics::RebuildDisplayMetrics(DisplayMetrics); + ScreenInfo.device_scale_factor = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(DisplayMetrics.PrimaryDisplayWorkAreaRect.Left, DisplayMetrics.PrimaryDisplayWorkAreaRect.Top); + } + return true; +} + + +#if !PLATFORM_LINUX +void FCEFInterfaceBrowserHandler::OnImeCompositionRangeChanged( + CefRefPtr<CefBrowser> Browser, + const CefRange& SelectionRange, + const CefRenderHandler::RectList& CharacterBounds) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + BrowserWindow->OnImeCompositionRangeChanged(Browser, SelectionRange, CharacterBounds); + } +} +#endif + +CefResourceRequestHandler::ReturnValue FCEFInterfaceBrowserHandler::OnBeforeResourceLoad(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, CefRefPtr<CefRequest> Request, CefRefPtr<CefRequestCallback> Callback) +{ + if (Request->IsReadOnly()) + { + LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnBeforeResourceLoad - readonly"); + + // we can't alter this request so just allow it through + return RV_CONTINUE; + } + + // Current thread is IO thread. We need to invoke BrowserWindow->GetResourceContent on the UI (aka Game) thread: + CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]() + { + const FString LanguageHeaderText(TEXT("Accept-Language")); + const FString LocaleCode = FWebInterfaceBrowserSingleton::GetCurrentLocaleCode(); + CefRequest::HeaderMap HeaderMap; + Request->GetHeaderMap(HeaderMap); + auto LanguageHeader = HeaderMap.find(TCHAR_TO_WCHAR(*LanguageHeaderText)); + if (LanguageHeader != HeaderMap.end()) + { + (*LanguageHeader).second = TCHAR_TO_WCHAR(*LocaleCode); + } + else + { + HeaderMap.insert(std::pair<CefString, CefString>(TCHAR_TO_WCHAR(*LanguageHeaderText), TCHAR_TO_WCHAR(*LocaleCode))); + } + + LOG_CEF_LOAD( "FCEFInterfaceBrowserHandler::OnBeforeResourceLoad" ); + + if (BeforeResourceLoadDelegate.IsBound()) + { + // Allow appending the Authorization header if this was NOT a RT_XHR type of page load + bool bAllowCredentials = URLRequestAllowsCredentials(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str())); + FRequestHeaders AdditionalHeaders; + BeforeResourceLoadDelegate.Execute(Request->GetURL(), Request->GetResourceType(), AdditionalHeaders, bAllowCredentials); + + for (auto Iter = AdditionalHeaders.CreateConstIterator(); Iter; ++Iter) + { + const FString& Header = Iter.Key(); + const FString& Value = Iter.Value(); + + HeaderMap.insert(std::pair<CefString, CefString>(TCHAR_TO_WCHAR(*Header), TCHAR_TO_WCHAR(*Value))); + } + } + + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + + if (BrowserWindow.IsValid()) + { + TOptional<FString> Contents = BrowserWindow->GetResourceContent(Frame, Request); + if(Contents.IsSet()) + { + Contents.GetValue().ReplaceInline(TEXT("\n"), TEXT(""), ESearchCase::CaseSensitive); + Contents.GetValue().ReplaceInline(TEXT("\r"), TEXT(""), ESearchCase::CaseSensitive); + + // pass the text we'd like to come back as a response to the request post data + CefRefPtr<CefPostData> PostData = CefPostData::Create(); + CefRefPtr<CefPostDataElement> Element = CefPostDataElement::Create(); + FTCHARToUTF8 UTF8String(*Contents.GetValue()); + Element->SetToBytes(UTF8String.Length(), UTF8String.Get()); + PostData->AddElement(Element); + Request->SetPostData(PostData); + + // Set a custom request header, so we know the mime type if it was specified as a hash on the dummy URL + std::string Url = Request->GetURL().ToString(); + std::string::size_type HashPos = Url.find_last_of('#'); + if (HashPos != std::string::npos) + { + std::string MimeType = Url.substr(HashPos + 1); + HeaderMap.insert(std::pair<CefString, CefString>(TCHAR_TO_WCHAR(TEXT("Content-Type")), MimeType)); + } + + // Change http method to tell GetResourceHandler to return the content + Request->SetMethod(TCHAR_TO_WCHAR(*CustomContentMethod)); + } + } + + if (Request->IsReadOnly()) + { + LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnBeforeResourceLoad - readonly"); + } + else + { + Request->SetHeaderMap(HeaderMap); + } + Callback->Continue(true); + })); + + // Tell CEF that we're handling this asynchronously. + return RV_CONTINUE_ASYNC; +} + +void FCEFInterfaceBrowserHandler::OnResourceLoadComplete( + CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + CefRefPtr<CefResponse> Response, + URLRequestStatus Status, + int64 Received_content_length) +{ + LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnResourceLoadComplete"); + + // Current thread is IO thread. We need to invoke our delegates on the UI (aka Game) thread: + CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]() + { + auto resType = Request->GetResourceType(); + const FString URL = WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()); + if (MainFrameLoadTypes.Contains(URL)) + { + // CEF has a bug where it confuses a MAIN_FRAME load for a XHR one, so fix it up here if we detect it. + resType = CefRequest::ResourceType::RT_MAIN_FRAME; + } + ResourceLoadCompleteDelegate.ExecuteIfBound(Request->GetURL(), resType, Status, Received_content_length); + + // this load is done, clear the request from our map + MainFrameLoadTypes.Remove(URL); + })); +} + +void FCEFInterfaceBrowserHandler::OnResourceRedirect(CefRefPtr<CefBrowser> browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + CefRefPtr<CefResponse> Response, + CefString& new_url) +{ + LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnResourceRedirect"); + // Current thread is IO thread. We need to invoke our delegates on the UI (aka Game) thread: + CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]() + { + // this load is effectively done, clear the request from our map + MainFrameLoadTypes.Remove(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str())); + })); +} + + +void FCEFInterfaceBrowserHandler::OnRenderProcessTerminated(CefRefPtr<CefBrowser> Browser, TerminationStatus Status) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + BrowserWindow->OnRenderProcessTerminated(Status); + } +} + +bool FCEFInterfaceBrowserHandler::OnBeforeBrowse(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + bool user_gesture, + bool IsRedirect) +{ + CefRequest::ResourceType RequestType = Request->GetResourceType(); + // We only want to append Authorization headers to main frame and similar requests + // BUGBUG - in theory we want to support XHR requests that have the access-control-allow-credentials header but CEF doesn't give us preflight details here + if (RequestType == CefRequest::ResourceType::RT_MAIN_FRAME || RequestType == CefRequest::ResourceType::RT_SUB_FRAME || RequestType == CefRequest::ResourceType::RT_SUB_RESOURCE) + { + // record that we saw this URL request as a main frame load + MainFrameLoadTypes.Add(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()), RequestType); + } + + // Current thread: UI thread + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::OnBeforeBrowse"); + if(BrowserWindow->OnBeforeBrowse(Browser, Frame, Request, user_gesture, IsRedirect)) + { + return true; + } + } + + return false; +} + +CefRefPtr<CefResourceHandler> FCEFInterfaceBrowserHandler::GetResourceHandler( CefRefPtr<CefBrowser> Browser, CefRefPtr< CefFrame > Frame, CefRefPtr< CefRequest > Request ) +{ + + if (Request->GetMethod() == TCHAR_TO_WCHAR(*CustomContentMethod)) + { + // Content override header will be set by OnBeforeResourceLoad before passing the request on to this. + if (Request->GetPostData() && Request->GetPostData()->GetElementCount() > 0) + { + // get the mime type from Content-Type header (default to text/html to support old behavior) + FString MimeType = TEXT("text/html"); // default if not specified + CefRequest::HeaderMap HeaderMap; + Request->GetHeaderMap(HeaderMap); + auto ContentOverride = HeaderMap.find(TCHAR_TO_WCHAR(TEXT("Content-Type"))); + if (ContentOverride != HeaderMap.end()) + { + MimeType = WCHAR_TO_TCHAR(ContentOverride->second.ToWString().c_str()); + } + + // reply with the post data + CefPostData::ElementVector Elements; + Request->GetPostData()->GetElements(Elements); + return new FCEFInterfaceBrowserByteResource(Elements[0], MimeType); + } + } + return nullptr; +} + +CefRefPtr<CefResourceRequestHandler> FCEFInterfaceBrowserHandler::GetResourceRequestHandler( CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, bool is_navigation, bool is_download, const CefString& request_initiator, bool& disable_default_handling) +{ + LOG_CEF_LOAD("FCEFInterfaceBrowserHandler::GetResourceRequestHandler"); + if (bInterceptLoadRequests) + return this; + return nullptr; +} + +void FCEFInterfaceBrowserHandler::SetBrowserWindow(TSharedPtr<FCEFWebInterfaceBrowserWindow> InBrowserWindow) +{ + BrowserWindowPtr = InBrowserWindow; + + if (InBrowserWindow.IsValid()) + { + // Register any JS bindings that are setup in the new browser. In theory there should be 0 here as we are still being created. + CefRefPtr<CefProcessMessage> SetValueMessage = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("CEF::STARTUP"))); + CefRefPtr<CefListValue> MessageArguments = SetValueMessage->GetArgumentList(); + CefRefPtr<CefDictionaryValue> Bindings = InBrowserWindow->GetProcessInfo(); + if (Bindings.get()) + { + MessageArguments->SetDictionary(0, Bindings); + } + InBrowserWindow->GetCefBrowser()->GetMainFrame()->SendProcessMessage(PID_RENDERER, SetValueMessage); + } +} + +bool FCEFInterfaceBrowserHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefProcessId SourceProcess, + CefRefPtr<CefProcessMessage> Message) +{ + bool Retval = false; + FString MessageName = WCHAR_TO_TCHAR(Message->GetName().ToWString().c_str()); + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + if (MessageName.StartsWith(TEXT("CEF::BROWSERCREATED"))) + { + // Register any JS bindings that are setup in the new browser. In theory there should be 0 here as we are still being created. + CefRefPtr<CefProcessMessage> SetValueMessage = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("CEF::STARTUP"))); + CefRefPtr<CefListValue> MessageArguments = SetValueMessage->GetArgumentList(); + CefRefPtr<CefDictionaryValue> Bindings = BrowserWindow->GetProcessInfo(); + if (Bindings.get()) + { + MessageArguments->SetDictionary(0, Bindings); + } + // CEF has a race condition for newly constructed browser objects, we may route this to the wrong renderer if we send right away + // so just PostTake to send this message next frame + CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]() + { + Frame->SendProcessMessage(PID_RENDERER, SetValueMessage); + })); + + } + else + { + Retval = BrowserWindow->OnProcessMessageReceived(Browser, Frame, SourceProcess, Message); + } + } + return Retval; +} + +bool FCEFInterfaceBrowserHandler::ShowDevTools(const CefRefPtr<CefBrowser>& Browser) +{ + CefPoint Point; + CefString TargetUrl = "chrome-devtools://devtools/devtools.html"; + CefString TargetFrameName = "devtools"; + CefPopupFeatures PopupFeatures; + CefWindowInfo WindowInfo; + CefRefPtr<CefClient> NewClient; + CefBrowserSettings BrowserSettings; + bool NoJavascriptAccess = false; + + PopupFeatures.xSet = false; + PopupFeatures.ySet = false; + PopupFeatures.heightSet = false; + PopupFeatures.widthSet = false; + PopupFeatures.menuBarVisible = false; + PopupFeatures.toolBarVisible = false; + PopupFeatures.statusBarVisible = false; + + // Set max framerate to maximum supported. + BrowserSettings.windowless_frame_rate = 60; + // Disable plugins + BrowserSettings.plugins = STATE_DISABLED; + // Dev Tools look best with a white background color + BrowserSettings.background_color = CefColorSetARGB(255, 255, 255, 255); + + // OnBeforePopup already takes care of all the details required to ask the host application to create a new browser window. + bool bSuppressWindowCreation = OnBeforePopup(Browser, Browser->GetFocusedFrame(), TargetUrl, TargetFrameName, PopupFeatures, WindowInfo, NewClient, BrowserSettings, &NoJavascriptAccess); + + if(! bSuppressWindowCreation) + { + Browser->GetHost()->ShowDevTools(WindowInfo, NewClient, BrowserSettings, Point); + } + return !bSuppressWindowCreation; +} + +bool FCEFInterfaceBrowserHandler::OnKeyEvent(CefRefPtr<CefBrowser> Browser, + const CefKeyEvent& Event, + CefEventHandle OsEvent) +{ + // Show dev tools on CMD/CTRL+SHIFT+I + if( (Event.type == KEYEVENT_RAWKEYDOWN || Event.type == KEYEVENT_KEYDOWN || Event.type == KEYEVENT_CHAR) && +#if PLATFORM_MAC + (Event.modifiers == (EVENTFLAG_COMMAND_DOWN | EVENTFLAG_SHIFT_DOWN)) && +#else + (Event.modifiers == (EVENTFLAG_CONTROL_DOWN | EVENTFLAG_SHIFT_DOWN)) && +#endif + (Event.windows_key_code == 'I' || + Event.unmodified_character == 'i' || Event.unmodified_character == 'I') && + IWebInterfaceBrowserModule::Get().GetSingleton()->IsDevToolsShortcutEnabled() + ) + { + return ShowDevTools(Browser); + } + +#if PLATFORM_MAC + // We need to handle standard Copy/Paste/etc... shortcuts on OS X + if( (Event.type == KEYEVENT_RAWKEYDOWN || Event.type == KEYEVENT_KEYDOWN) && + (Event.modifiers & EVENTFLAG_COMMAND_DOWN) != 0 && + (Event.modifiers & EVENTFLAG_CONTROL_DOWN) == 0 && + (Event.modifiers & EVENTFLAG_ALT_DOWN) == 0 && + ( (Event.modifiers & EVENTFLAG_SHIFT_DOWN) == 0 || Event.unmodified_character == 'z' ) + ) + { + CefRefPtr<CefFrame> Frame = Browser->GetFocusedFrame(); + if (Frame) + { + switch (Event.unmodified_character) + { + case 'a': + Frame->SelectAll(); + return true; + case 'c': + Frame->Copy(); + return true; + case 'v': + Frame->Paste(); + return true; + case 'x': + Frame->Cut(); + return true; + case 'z': + if( (Event.modifiers & EVENTFLAG_SHIFT_DOWN) == 0 ) + { + Frame->Undo(); + } + else + { + Frame->Redo(); + } + return true; + } + } + } +#endif + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + return BrowserWindow->OnUnhandledKeyEvent(Event); + } + + return false; +} + +bool FCEFInterfaceBrowserHandler::OnJSDialog(CefRefPtr<CefBrowser> Browser, const CefString& OriginUrl, JSDialogType DialogType, const CefString& MessageText, const CefString& DefaultPromptText, CefRefPtr<CefJSDialogCallback> Callback, bool& OutSuppressMessage) +{ + bool Retval = false; + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + Retval = BrowserWindow->OnJSDialog(DialogType, MessageText, DefaultPromptText, Callback, OutSuppressMessage); + } + return Retval; +} + +bool FCEFInterfaceBrowserHandler::OnBeforeUnloadDialog(CefRefPtr<CefBrowser> Browser, const CefString& MessageText, bool IsReload, CefRefPtr<CefJSDialogCallback> Callback) +{ + bool Retval = false; + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + Retval = BrowserWindow->OnBeforeUnloadDialog(MessageText, IsReload, Callback); + } + return Retval; +} + +void FCEFInterfaceBrowserHandler::OnResetDialogState(CefRefPtr<CefBrowser> Browser) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + BrowserWindow->OnResetDialogState(); + } +} + +void FCEFInterfaceBrowserHandler::OnBeforeContextMenu(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, CefRefPtr<CefContextMenuParams> Params, CefRefPtr<CefMenuModel> Model) +{ + Model->Clear(); +} + +void FCEFInterfaceBrowserHandler::OnDraggableRegionsChanged(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> frame, const std::vector<CefDraggableRegion>& Regions) +{ + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if (BrowserWindow.IsValid()) + { + TArray<FWebInterfaceBrowserDragRegion> DragRegions; + for (uint32 Idx = 0; Idx < Regions.size(); Idx++) + { + DragRegions.Add(FWebInterfaceBrowserDragRegion( + FIntRect(Regions[Idx].bounds.x, Regions[Idx].bounds.y, Regions[Idx].bounds.x + Regions[Idx].bounds.width, Regions[Idx].bounds.y + Regions[Idx].bounds.height), + Regions[Idx].draggable ? true : false)); + } + BrowserWindow->UpdateDragRegions(DragRegions); + } +} + +CefRefPtr<CefCookieAccessFilter> FCEFInterfaceBrowserHandler::GetCookieAccessFilter( + CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request) +{ + FString Url = WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()); + TArray<FString> UrlParts; + if (Url.ParseIntoArray(UrlParts, TEXT("/"), true) >= 2) + { + if (UrlParts[1].Contains(TEXT(".epicgames.com")) || UrlParts[1].Contains(TEXT(".epicgames.net"))) + { + // We only support custom cookie alteration for the epicgames domains right now. + // There are limitations/bugs in CEF when the cookie filtering it on making it fail to pass cookies for some requests, so + // we want to limit the scope of the filtering. See https://jira.it.epicgames.com/browse/DISTRO-1847 as an example of a bug + // caused by filtering + return this; + } + } + + return nullptr; +} + +bool FCEFInterfaceBrowserHandler::CanSaveCookie(CefRefPtr<CefBrowser> browser, + CefRefPtr<CefFrame> frame, + CefRefPtr<CefRequest> request, + CefRefPtr<CefResponse> response, + const CefCookie& cookie) +{ + if (bAllowAllCookies) + { + return true; + } + + // these two cookies shouldn't be saved by the client. While we are debugging why the backend is causing them to be set filter them out + if (CefString(&cookie.name).ToString() == "store-token" || CefString(&cookie.name) == "EPIC_SESSION_DIESEL") + return false; + return true; +} + +bool FCEFInterfaceBrowserHandler::CanSendCookie(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + const CefCookie& Cookie) +{ + if (bAllowAllCookies) + { + return true; + } + + FString RequestURL(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str())); + FString ReffererURL(WCHAR_TO_TCHAR(Request->GetReferrerURL().ToWString().c_str())); + if (ReffererURL.Contains("marketplace-website-node-launcher-") && RequestURL.Contains("graphql.epicgames.com")) + { + // requests from the marketplace UE4 page to graphql can exceed the header size limits so manually prune this large cookie here + if (CefString(&Cookie.name).ToString() == "ecma") + return false; + } + return true; +} + + +bool FCEFInterfaceBrowserHandler::URLRequestAllowsCredentials(const FString& URL) const +{ + // if we inserted this URL into our map then we want to allow credentials for it + if (MainFrameLoadTypes.Find(URL) != nullptr) + return true; + + // check the explicit allowlist also + for (const FString& AuthorizationHeaderAllowListURL : AuthorizationHeaderAllowListURLS) + { + if (URL.Contains(AuthorizationHeaderAllowListURL)) + { + return true; + } + } + return false; +} + + +#undef LOCTEXT_NAMESPACE + +#endif // WITH_CEF diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserHandler.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..d04ad247a9bde396dc226f45beefb13d1580140a --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserHandler.h @@ -0,0 +1,398 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserHandler.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 + + +#include "CEFInterfaceLibCefIncludes.h" + + +#include "IWebInterfaceBrowserWindow.h" + +#endif + +class IWebInterfaceBrowserWindow; +struct Rect; +class FCEFWebInterfaceBrowserWindow; +class FCEFInterfaceBrowserPopupFeatures; + +#if WITH_CEF3 + +/** + * Implements CEF Client and other Browser level interfaces. + */ +class FCEFInterfaceBrowserHandler + : public CefClient + , public CefDisplayHandler + , public CefLifeSpanHandler + , public CefLoadHandler + , public CefRenderHandler + , public CefRequestHandler + , public CefKeyboardHandler + , public CefJSDialogHandler + , public CefContextMenuHandler + , public CefDragHandler + , public CefResourceRequestHandler + , public CefRequestContextHandler + , public CefCookieAccessFilter +{ +public: + + /** Default constructor. */ + FCEFInterfaceBrowserHandler(bool InUseTransparency, bool InInterceptLoadRequests, const TArray<FString>& AltRetryDomains = TArray<FString>(), const TArray<FString>& AuthorizationHeaderAllowListURLS = TArray<FString>()); + +public: + + /** + * Pass in a pointer to our Browser Window so that events can be passed on. + * + * @param InBrowserWindow The browser window this will be handling. + */ + void SetBrowserWindow(TSharedPtr<FCEFWebInterfaceBrowserWindow> InBrowserWindow); + + /** + * Sets the browser window features and settings for popups which will be passed along when creating the new window. + * + * @param InPopupFeatures The popup features and settings for the window. + */ + void SetPopupFeatures(const TSharedPtr<FCEFInterfaceBrowserPopupFeatures>& InPopupFeatures) + { + BrowserPopupFeatures = InPopupFeatures; + } + +public: + + // CefClient Interface + + virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() override + { + return this; + } + + virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() override + { + return this; + } + + virtual CefRefPtr<CefLoadHandler> GetLoadHandler() override + { + return this; + } + + virtual CefRefPtr<CefRenderHandler> GetRenderHandler() override + { + return this; + } + + virtual CefRefPtr<CefRequestHandler> GetRequestHandler() override + { + return this; + } + + virtual CefRefPtr<CefKeyboardHandler> GetKeyboardHandler() override + { + return this; + } + + virtual CefRefPtr<CefJSDialogHandler> GetJSDialogHandler() override + { + return this; + } + + virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() override + { + return this; + } + + virtual CefRefPtr<CefDragHandler> GetDragHandler() override + { + return this; + } + + virtual CefRefPtr<CefCookieAccessFilter> GetCookieAccessFilter( + CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request) override; + + virtual bool OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> frame, + CefProcessId SourceProcess, + CefRefPtr<CefProcessMessage> Message) override; + +public: + + // CefDisplayHandler Interface + + virtual void OnTitleChange(CefRefPtr<CefBrowser> Browser, const CefString& Title) override; + virtual void OnAddressChange(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, const CefString& Url) override; + virtual bool OnTooltip(CefRefPtr<CefBrowser> Browser, CefString& Text) override; + virtual bool OnConsoleMessage( + CefRefPtr<CefBrowser> Browser, + cef_log_severity_t level, + const CefString& Message, + const CefString& Source, + int Line) override; + +public: + + // CefLifeSpanHandler Interface + + virtual void OnAfterCreated(CefRefPtr<CefBrowser> Browser) override; + virtual bool DoClose(CefRefPtr<CefBrowser> Browser) override; + virtual void OnBeforeClose(CefRefPtr<CefBrowser> Browser) override; + + virtual bool OnBeforePopup(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + const CefString& Target_Url, + const CefString& Target_Frame_Name, + CefLifeSpanHandler::WindowOpenDisposition /* Target_Disposition */, + bool /* User_Gesture */, + const CefPopupFeatures& PopupFeatures, + CefWindowInfo& WindowInfo, + CefRefPtr<CefClient>& Client, + CefBrowserSettings& Settings, + CefRefPtr<CefDictionaryValue>& extra_info, + bool* no_javascript_access) override + { + return OnBeforePopup(Browser, Frame, Target_Url, Target_Frame_Name, PopupFeatures, WindowInfo, Client, Settings, no_javascript_access); + } + + virtual bool OnBeforePopup(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + const CefString& Target_Url, + const CefString& Target_Frame_Name, + const CefPopupFeatures& PopupFeatures, + CefWindowInfo& WindowInfo, + CefRefPtr<CefClient>& Client, + CefBrowserSettings& Settings, + bool* no_javascript_access) ; + +public: + + // CefLoadHandler Interface + + virtual void OnLoadError(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefLoadHandler::ErrorCode InErrorCode, + const CefString& ErrorText, + const CefString& FailedUrl) override; + + virtual void OnLoadingStateChange( + CefRefPtr<CefBrowser> browser, + bool isLoading, + bool canGoBack, + bool canGoForward) override; + + virtual void OnLoadStart( + CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + TransitionType CefTransitionType) override; + +public: + + // CefRenderHandler Interface + virtual bool GetRootScreenRect(CefRefPtr<CefBrowser> Browser, CefRect& Rect) override; + virtual void GetViewRect(CefRefPtr<CefBrowser> Browser, CefRect& Rect) override; + virtual void OnPaint(CefRefPtr<CefBrowser> Browser, + PaintElementType Type, + const RectList& DirtyRects, + const void* Buffer, + int Width, int Height) override; + virtual void OnAcceleratedPaint(CefRefPtr<CefBrowser> Browser, + PaintElementType Type, + const RectList& DirtyRects, + void* SharedHandle) override; + virtual void OnPopupShow(CefRefPtr<CefBrowser> Browser, bool bShow) override; + virtual void OnPopupSize(CefRefPtr<CefBrowser> Browser, const CefRect& Rect) override; + virtual bool GetScreenInfo(CefRefPtr<CefBrowser> Browser, CefScreenInfo& ScreenInfo) override; + // CefDisplayHandler interface + virtual bool OnCursorChange(CefRefPtr<CefBrowser> browser, + CefCursorHandle cursor, + cef_cursor_type_t type, + const CefCursorInfo& custom_cursor_info) override; +#if !PLATFORM_LINUX + virtual void OnImeCompositionRangeChanged( + CefRefPtr<CefBrowser> Browser, + const CefRange& SelectionRange, + const CefRenderHandler::RectList& CharacterBounds) override; +#endif + +public: + + // CefRequestHandler Interface + + virtual CefResourceRequestHandler::ReturnValue OnBeforeResourceLoad( + CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + CefRefPtr<CefRequestCallback> Callback) override; + virtual void OnResourceLoadComplete(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + CefRefPtr<CefResponse> Response, + URLRequestStatus Status, + int64 Received_content_length) override; + virtual void OnResourceRedirect(CefRefPtr<CefBrowser> browser, + CefRefPtr<CefFrame> frame, + CefRefPtr<CefRequest> request, + CefRefPtr<CefResponse> response, + CefString& new_url) override; + virtual void OnRenderProcessTerminated(CefRefPtr<CefBrowser> Browser, TerminationStatus Status) override; + virtual bool OnBeforeBrowse(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + bool user_gesture, + bool IsRedirect) override; + virtual CefRefPtr<CefResourceHandler> GetResourceHandler( + CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request ) override; + virtual bool OnCertificateError( + CefRefPtr<CefBrowser> Browser, + cef_errorcode_t CertError, + const CefString& RequestUrl, + CefRefPtr<CefSSLInfo> SslInfo, + CefRefPtr<CefRequestCallback> Callback ) override; + + virtual CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler( + CefRefPtr<CefBrowser> browser, + CefRefPtr<CefFrame> frame, + CefRefPtr<CefRequest> request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling) override; + +public: + // CefKeyboardHandler interface + virtual bool OnKeyEvent(CefRefPtr<CefBrowser> Browser, + const CefKeyEvent& Event, + CefEventHandle OsEvent) override; + +public: + // CefCookieAccessFilter interface + virtual bool CanSaveCookie(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + CefRefPtr<CefResponse> Response, + const CefCookie& Cookie) override; + + virtual bool CanSendCookie(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + const CefCookie& Cookie) override; + +public: + // CefJSDialogHandler interface + + virtual bool OnJSDialog( + CefRefPtr<CefBrowser> Browser, + const CefString& OriginUrl, + JSDialogType DialogType, + const CefString& MessageText, + const CefString& DefaultPromptText, + CefRefPtr<CefJSDialogCallback> Callback, + bool& OutSuppressMessage) override; + + virtual bool OnBeforeUnloadDialog(CefRefPtr<CefBrowser> Browser, const CefString& MessageText, bool IsReload, CefRefPtr<CefJSDialogCallback> Callback) override; + + virtual void OnResetDialogState(CefRefPtr<CefBrowser> Browser) override; + +public: + // CefContextMenuHandler + + virtual void OnBeforeContextMenu(CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefContextMenuParams> Params, + CefRefPtr<CefMenuModel> Model) override; + +public: + // CefDragHandler interface + + virtual void OnDraggableRegionsChanged( + CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> frame, + const std::vector<CefDraggableRegion>& Regions) override; + +public: + + IWebInterfaceBrowserWindow::FOnBeforePopupDelegate& OnBeforePopup() + { + return BeforePopupDelegate; + } + + IWebInterfaceBrowserWindow::FOnCreateWindow& OnCreateWindow() + { + return CreateWindowDelegate; + } + + typedef TMap<FString, FString> FRequestHeaders; + DECLARE_DELEGATE_FourParams(FOnBeforeResourceLoadDelegate, const CefString& /*URL*/, CefRequest::ResourceType /*Type*/, FRequestHeaders& /*AdditionalHeaders*/, const bool /*AllowUserCredentials*/); + FOnBeforeResourceLoadDelegate& OnBeforeResourceLoad() + { + return BeforeResourceLoadDelegate; + } + + DECLARE_DELEGATE_FourParams(FOnResourceLoadCompleteDelegate, const CefString& /*URL*/, CefRequest::ResourceType /*Type*/, CefResourceRequestHandler::URLRequestStatus /*Status*/, int64 /*ContentLength*/); + FOnResourceLoadCompleteDelegate& OnResourceLoadComplete() + { + return ResourceLoadCompleteDelegate; + } + + DECLARE_DELEGATE_FiveParams(FOnConsoleMessageDelegate, CefRefPtr<CefBrowser> /*Browser*/, cef_log_severity_t /*level*/, const CefString& /*Message*/, const CefString& /*Source*/, int32 /*Line*/); + FOnConsoleMessageDelegate& OnConsoleMessage() + { + return ConsoleMessageDelegate; + } + + bool URLRequestAllowsCredentials(const FString& URL) const; + +private: + + bool ShowDevTools(const CefRefPtr<CefBrowser>& Browser); + + bool bUseTransparency; + bool bAllowAllCookies; + bool bInterceptLoadRequests; + + TArray<FString> AltRetryDomains; + uint32 AltRetryDomainIdx = 0; + + /** Domains we allow sending an authorization header too even if the request doesn't otherwise indicate support */ + TArray<FString> AuthorizationHeaderAllowListURLS; + + /** Keep track of URLs we see being loaded and the type of load it is*/ + TMap<FString, CefRequest::ResourceType> MainFrameLoadTypes; + + /** Delegate for notifying that a popup window is attempting to open. */ + IWebInterfaceBrowserWindow::FOnBeforePopupDelegate BeforePopupDelegate; + + /** Delegate for handling requests to create new windows. */ + IWebInterfaceBrowserWindow::FOnCreateWindow CreateWindowDelegate; + + /** Delegate for handling adding additional headers to requests */ + FOnBeforeResourceLoadDelegate BeforeResourceLoadDelegate; + + /** Delegate that allows response to the status of resource loads */ + FOnResourceLoadCompleteDelegate ResourceLoadCompleteDelegate; + + /** Delegate that allows for response to console logs. Typically used to capture and mirror web logs in client application logs. */ + FOnConsoleMessageDelegate ConsoleMessageDelegate; + + /** Weak Pointer to our Web Browser window so that events can be passed on while it's valid.*/ + TWeakPtr<FCEFWebInterfaceBrowserWindow> BrowserWindowPtr; + + /** Pointer to the parent web browser handler */ + CefRefPtr<FCEFInterfaceBrowserHandler> ParentHandler; + + /** Stores popup window features and settings */ + TSharedPtr<FCEFInterfaceBrowserPopupFeatures> BrowserPopupFeatures; + + // Include the default reference counting implementation. + IMPLEMENT_REFCOUNTING(FCEFInterfaceBrowserHandler); +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserPopupFeatures.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserPopupFeatures.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c96050bf443eb40e4555af7f9164ae9c40312c92 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserPopupFeatures.cpp @@ -0,0 +1,145 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserPopupFeatures.cpp + +#include "CEF/CEFInterfaceBrowserPopupFeatures.h" + +#if WITH_CEF3 + +FCEFInterfaceBrowserPopupFeatures::FCEFInterfaceBrowserPopupFeatures() + : X(0) + , bXSet(false) + , Y(0) + , bYSet(false) + , Width(0) + , bWidthSet(false) + , Height(0) + , bHeightSet(false) + , bMenuBarVisible(true) + , bStatusBarVisible(false) + , bToolBarVisible(true) + , bLocationBarVisible(true) + , bScrollbarsVisible(true) + , bResizable(true) + , bIsFullscreen(false) + , bIsDialog(false) +{ +} + + +FCEFInterfaceBrowserPopupFeatures::FCEFInterfaceBrowserPopupFeatures( const CefPopupFeatures& PopupFeatures ) +{ + X = PopupFeatures.x; + bXSet = PopupFeatures.xSet ? true : false; + Y = PopupFeatures.y; + bYSet = PopupFeatures.ySet ? true : false; + Width = PopupFeatures.width; + bWidthSet = PopupFeatures.widthSet ? true : false; + Height = PopupFeatures.height; + bHeightSet = PopupFeatures.heightSet ? true : false; + bMenuBarVisible = PopupFeatures.menuBarVisible ? true : false; + bStatusBarVisible = PopupFeatures.statusBarVisible ? true : false; + bToolBarVisible = PopupFeatures.toolBarVisible ? true : false; + bScrollbarsVisible = PopupFeatures.scrollbarsVisible ? true : false; + + // no longer set by the CEF API so default them here to their historic value + bLocationBarVisible = false; + bResizable = false; + bIsFullscreen = false; + bIsDialog = false; +} + +FCEFInterfaceBrowserPopupFeatures::~FCEFInterfaceBrowserPopupFeatures() +{ +} + +void FCEFInterfaceBrowserPopupFeatures::SetResizable(const bool bResize) +{ + bResizable = bResize; +} + +int FCEFInterfaceBrowserPopupFeatures::GetX() const +{ + return X; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsXSet() const +{ + return bXSet; +} + +int FCEFInterfaceBrowserPopupFeatures::GetY() const +{ + return Y; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsYSet() const +{ + return bYSet; +} + +int FCEFInterfaceBrowserPopupFeatures::GetWidth() const +{ + return Width; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsWidthSet() const +{ + return bWidthSet; +} + +int FCEFInterfaceBrowserPopupFeatures::GetHeight() const +{ + return Height; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsHeightSet() const +{ + return bHeightSet; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsMenuBarVisible() const +{ + return bMenuBarVisible; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsStatusBarVisible() const +{ + return bStatusBarVisible; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsToolBarVisible() const +{ + return bToolBarVisible; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsLocationBarVisible() const +{ + return bLocationBarVisible; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsScrollbarsVisible() const +{ + return bScrollbarsVisible; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsResizable() const +{ + return bResizable; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsFullscreen() const +{ + return bIsFullscreen; +} + +bool FCEFInterfaceBrowserPopupFeatures::IsDialog() const +{ + return bIsDialog; +} + +TArray<FString> FCEFInterfaceBrowserPopupFeatures::GetAdditionalFeatures() const +{ + TArray<FString> Empty; + return Empty; +} + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserPopupFeatures.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserPopupFeatures.h new file mode 100644 index 0000000000000000000000000000000000000000..9c3e4ab264c6701cba7e8b06df25c6eceddc2dde --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceBrowserPopupFeatures.h @@ -0,0 +1,92 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFBrowserPopupFeatures.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 + +#include "IWebInterfaceBrowserPopupFeatures.h" + +#if PLATFORM_WINDOWS +#include "Windows/WindowsHWrapper.h" +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/AllowWindowsPlatformAtomics.h" +#endif + +#pragma push_macro("OVERRIDE") +#undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +#include "include/cef_base.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +#pragma pop_macro("OVERRIDE") + +#if PLATFORM_WINDOWS +#include "Windows/HideWindowsPlatformAtomics.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif + +#endif + +class IWebInterfaceBrowserPopupFeatures; + +#if WITH_CEF3 + +class FCEFInterfaceBrowserPopupFeatures + : public IWebInterfaceBrowserPopupFeatures +{ +public: + FCEFInterfaceBrowserPopupFeatures(); + FCEFInterfaceBrowserPopupFeatures(const CefPopupFeatures& PopupFeatures); + virtual ~FCEFInterfaceBrowserPopupFeatures(); + void SetResizable(const bool bResize); + + // IWebBrowserPopupFeatures Interface + + virtual int GetX() const override; + virtual bool IsXSet() const override; + virtual int GetY() const override; + virtual bool IsYSet() const override; + virtual int GetWidth() const override; + virtual bool IsWidthSet() const override; + virtual int GetHeight() const override; + virtual bool IsHeightSet() const override; + virtual bool IsMenuBarVisible() const override; + virtual bool IsStatusBarVisible() const override; + virtual bool IsToolBarVisible() const override; + virtual bool IsLocationBarVisible() const override; + virtual bool IsScrollbarsVisible() const override; + virtual bool IsResizable() const override; + virtual bool IsFullscreen() const override; + virtual bool IsDialog() const override; + virtual TArray<FString> GetAdditionalFeatures() const override; + +private: + + int X; + bool bXSet; + int Y; + bool bYSet; + int Width; + bool bWidthSet; + int Height; + bool bHeightSet; + + bool bMenuBarVisible; + bool bStatusBarVisible; + bool bToolBarVisible; + bool bLocationBarVisible; + bool bScrollbarsVisible; + bool bResizable; + + bool bIsFullscreen; + bool bIsDialog; +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceCookieManager.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceCookieManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..b68028c6837b96ef5cbf092d3002a94c3b46d068 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceCookieManager.cpp @@ -0,0 +1,172 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFCookieManager.cpp + +#include "CoreTypes.h" +#include "Containers/ContainersFwd.h" +#if WITH_CEF3 +#include "IWebInterfaceBrowserCookieManager.h" +#include "WebInterfaceBrowserSingleton.h" + +#include "CEFInterfaceLibCefIncludes.h" + +class FCefInterfaceCookieManager + : public IWebInterfaceBrowserCookieManager +{ +public: + + virtual ~FCefInterfaceCookieManager() + { } + + virtual void SetCookie(const FString& URL, const FCookie& Cookie, TFunction<void(bool)> Completed) override + { + CefRefPtr<FSetCookieFunctionCallback> Callback = Completed ? new FSetCookieFunctionCallback(Completed) : nullptr; + + CefCookie CefCookie; + CefString(&CefCookie.name) = TCHAR_TO_WCHAR(*Cookie.Name); + CefString(&CefCookie.value) = TCHAR_TO_WCHAR(*Cookie.Value); + CefString(&CefCookie.domain) = TCHAR_TO_WCHAR(*Cookie.Domain); + CefString(&CefCookie.path) = TCHAR_TO_WCHAR(*Cookie.Path); + CefCookie.secure = Cookie.bSecure; + CefCookie.httponly = Cookie.bHttpOnly; + CefCookie.has_expires = Cookie.bHasExpires; + + cef_time_t CefTime; + CefTime.year = Cookie.Expires.GetYear(); + CefTime.month = Cookie.Expires.GetMonth(); + CefTime.day_of_month = Cookie.Expires.GetDay(); + CefTime.hour = Cookie.Expires.GetHour(); + CefTime.minute = Cookie.Expires.GetMinute(); + CefTime.second = Cookie.Expires.GetSecond(); + CefTime.millisecond = Cookie.Expires.GetMillisecond(); + + const EDayOfWeek DayOfWeek = Cookie.Expires.GetDayOfWeek(); + + if (DayOfWeek == EDayOfWeek::Sunday) // some crazy reason our date/time class think Monday is the beginning of the week + { + CefTime.day_of_week = 0; + } + else + { + CefTime.day_of_week = ((int32)DayOfWeek) + 1; + } + + CefCookie.expires = CefTime; + + if (!CookieManager->SetCookie(TCHAR_TO_WCHAR(*URL), CefCookie, Callback)) + { + Callback->OnComplete(false); + } + } + + virtual void DeleteCookies(const FString& URL, const FString& CookieName, TFunction<void(int)> Completed) override + { + CefRefPtr<FDeleteCookiesFunctionCallback> Callback = Completed ? new FDeleteCookiesFunctionCallback(Completed) : nullptr; + if (!CookieManager->DeleteCookies(TCHAR_TO_WCHAR(*URL), TCHAR_TO_WCHAR(*CookieName), Callback)) + { + Callback->OnComplete(-1); + } + } + +private: + + FCefInterfaceCookieManager( + const CefRefPtr<CefCookieManager>& InCookieManager) + : CookieManager(InCookieManager) + { } + + // Callback that will invoke the callback with the result on the UI thread. + class FDeleteCookiesFunctionCallback + : public CefDeleteCookiesCallback + { + TFunction<void(int)> Callback; + public: + FDeleteCookiesFunctionCallback(const TFunction<void(int)>& InCallback) + : Callback(InCallback) + {} + + virtual void OnComplete(int NumDeleted) override + { + // We're on the IO thread, so we'll have to schedule the callback on the main thread + CefPostTask(TID_UI, new FDeleteCookiesNotificationTask(Callback, NumDeleted)); + } + + IMPLEMENT_REFCOUNTING(FDeleteCookiesFunctionCallback); + }; + + // Callback that will invoke the callback with the result on the UI thread. + class FSetCookieFunctionCallback + : public CefSetCookieCallback + { + TFunction<void(bool)> Callback; + public: + FSetCookieFunctionCallback(const TFunction<void(bool)>& InCallback) + : Callback(InCallback) + {} + + virtual void OnComplete(bool bSuccess) override + { + // We're on the IO thread, so we'll have to schedule the callback on the main thread + CefPostTask(TID_UI, new FSetCookieNotificationTask(Callback, bSuccess)); + } + + IMPLEMENT_REFCOUNTING(FSetCookieFunctionCallback); + }; + + // Task for executing a callback on a given thread. + class FDeleteCookiesNotificationTask + : public CefTask + { + TFunction<void(int)> Callback; + int Value; + + public: + + FDeleteCookiesNotificationTask(const TFunction<void(int)>& InCallback, int InValue) + : Callback(InCallback) + , Value(InValue) + {} + + virtual void Execute() override + { + Callback(Value); + } + + IMPLEMENT_REFCOUNTING(FDeleteCookiesNotificationTask); + }; + + // Task for executing a callback on a given thread. + class FSetCookieNotificationTask + : public CefTask + { + TFunction<void(bool)> Callback; + bool bSuccess; + + public: + + FSetCookieNotificationTask(const TFunction<void(bool)>& InCallback, bool InSuccess) + : Callback(InCallback) + , bSuccess(InSuccess) + {} + + virtual void Execute() override + { + Callback(bSuccess); + } + + IMPLEMENT_REFCOUNTING(FSetCookieNotificationTask); + }; + +private: + + const CefRefPtr<CefCookieManager> CookieManager; + + friend FCefWebInterfaceCookieManagerFactory; +}; + +TSharedRef<IWebInterfaceBrowserCookieManager> FCefWebInterfaceCookieManagerFactory::Create( + const CefRefPtr<CefCookieManager>& CookieManager) +{ + return MakeShareable(new FCefInterfaceCookieManager( + CookieManager)); +} + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceImeHandler.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceImeHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..c37625db5a931da9bba2d04cc8491b7df7dfa8df --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceImeHandler.cpp @@ -0,0 +1,211 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFImeHandler.cpp + +#include "CEF/CEFInterfaceImeHandler.h" + +#if WITH_CEF3 && !PLATFORM_LINUX + +#include "CEFInterfaceTextInputMethodContext.h" + + +FCEFInterfaceImeHandler::FCEFInterfaceImeHandler(CefRefPtr<CefBrowser> Browser) + : InternalCefBrowser(Browser) + , TextInputMethodSystem(nullptr) +{ + +} + +bool FCEFInterfaceImeHandler::OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, CefProcessId SourceProcess, CefRefPtr<CefProcessMessage> Message) +{ + bool Result = false; + FString MessageName = WCHAR_TO_TCHAR(Message->GetName().ToWString().c_str()); + if (MessageName == TEXT("UE::IME::FocusChanged")) + { + Result = HandleFocusChangedMessage(Message->GetArgumentList()); + } + return Result; +} + +void FCEFInterfaceImeHandler::SendProcessMessage(CefRefPtr<CefProcessMessage> Message) +{ + if (IsValid() && InternalCefBrowser->GetMainFrame()) + { + InternalCefBrowser->GetMainFrame()->SendProcessMessage(PID_RENDERER, Message); + } +} + +void FCEFInterfaceImeHandler::BindInputMethodSystem(ITextInputMethodSystem* InTextInputMethodSystem) +{ + TextInputMethodSystem = InTextInputMethodSystem; + if (TextInputMethodSystem && TextInputMethodContext.IsValid()) + { + TextInputMethodChangeNotifier = TextInputMethodSystem->RegisterContext(TextInputMethodContext.ToSharedRef()); + } +} + +void FCEFInterfaceImeHandler::UnbindInputMethodSystem() +{ + if (TextInputMethodContext.IsValid()) + { + DestroyContext(); + } + TextInputMethodSystem = nullptr; +} + +void FCEFInterfaceImeHandler::InitContext() +{ + if (!TextInputMethodSystem) + { + return; + } + + // Clean up any existing context + DestroyContext(); + + TextInputMethodContext = FCEFInterfaceTextInputMethodContext::Create(SharedThis(this)); + if (TextInputMethodSystem) + { + TextInputMethodChangeNotifier = TextInputMethodSystem->RegisterContext(TextInputMethodContext.ToSharedRef()); + } + if (TextInputMethodChangeNotifier.IsValid()) + { + TextInputMethodChangeNotifier->NotifyLayoutChanged(ITextInputMethodChangeNotifier::ELayoutChangeType::Created); + } + + TextInputMethodSystem->ActivateContext(TextInputMethodContext.ToSharedRef()); +} + +void FCEFInterfaceImeHandler::DeactivateContext() +{ + if (!TextInputMethodSystem || !TextInputMethodContext.IsValid()) + { + return; + } + + TSharedRef<FCEFInterfaceTextInputMethodContext> TextInputMethodContextRef = TextInputMethodContext.ToSharedRef(); + if (TextInputMethodSystem->IsActiveContext(TextInputMethodContextRef)) + { + // Make sure that the composition is aborted to avoid any IME calls to EndComposition from trying to mutate our dying owner widget + if (TextInputMethodContextRef->IsComposing()) + { + TextInputMethodContextRef->AbortComposition(); + if (TextInputMethodChangeNotifier.IsValid()) + { + TextInputMethodChangeNotifier->CancelComposition(); + } + } + TextInputMethodSystem->DeactivateContext(TextInputMethodContextRef); + } +} + +void FCEFInterfaceImeHandler::DestroyContext() +{ + if (!TextInputMethodContext.IsValid()) + { + return; + } + + if (TextInputMethodSystem) + { + DeactivateContext(); + TextInputMethodSystem->UnregisterContext(TextInputMethodContext.ToSharedRef()); + } + + TextInputMethodChangeNotifier.Reset(); + TextInputMethodContext.Reset(); +} + +bool FCEFInterfaceImeHandler::HandleFocusChangedMessage(CefRefPtr<CefListValue> MessageArguments) +{ + if (MessageArguments->GetSize() == 1 && + MessageArguments->GetType(0) == VTYPE_STRING) + { + if (TextInputMethodContext.IsValid()) + { + DestroyContext(); + } + return true; + } + else if (MessageArguments->GetSize() == 8 && + MessageArguments->GetType(0) == VTYPE_STRING && + MessageArguments->GetType(1) == VTYPE_STRING && + MessageArguments->GetType(2) == VTYPE_BOOL && + MessageArguments->GetType(3) == VTYPE_STRING && + MessageArguments->GetType(4) == VTYPE_INT && + MessageArguments->GetType(5) == VTYPE_INT && + MessageArguments->GetType(6) == VTYPE_INT && + MessageArguments->GetType(7) == VTYPE_INT) + { + FString Type = WCHAR_TO_TCHAR(MessageArguments->GetString(0).ToWString().c_str()); + FString Name = WCHAR_TO_TCHAR(MessageArguments->GetString(1).ToWString().c_str()); + bool bIsEditable = MessageArguments->GetBool(2); + + if (Type == TEXT("DOM_NODE_TYPE_ELEMENT") && + (Name == TEXT("INPUT") || Name == TEXT("TEXTAREA")) && + bIsEditable) + { + // @todo: Make use of the bounds of the text box here to act as a fallback for the initial composition window pos. + InitContext(); + } + return true; + } + + return false; +} + +void FCEFInterfaceImeHandler::UnbindCefBrowser() +{ + if (TextInputMethodContext.IsValid()) + { + DestroyContext(); + } + InternalCefBrowser = nullptr; +} + + +void FCEFInterfaceImeHandler::CacheBrowserSlateInfo(const TSharedRef<SWidget>& Widget) +{ + InternalBrowserSlateWidget = Widget; +} + +void FCEFInterfaceImeHandler::SetFocus(bool bHasFocus) +{ + if (!TextInputMethodSystem || !TextInputMethodContext.IsValid()) + { + return; + } + + if (bHasFocus) + { + TextInputMethodSystem->ActivateContext(TextInputMethodContext.ToSharedRef()); + } + else + { + DeactivateContext(); + } +} + +void FCEFInterfaceImeHandler::UpdateCachedGeometry(const FGeometry& AllottedGeometry) +{ + if (TextInputMethodContext.IsValid() && + TextInputMethodContext->UpdateCachedGeometry(AllottedGeometry)) + { + if (TextInputMethodChangeNotifier.IsValid()) + { + TextInputMethodChangeNotifier->NotifyLayoutChanged(ITextInputMethodChangeNotifier::ELayoutChangeType::Changed); + } + } +} + +void FCEFInterfaceImeHandler::CEFCompositionRangeChanged(const CefRange& SelectionRange, const CefRenderHandler::RectList& CharacterBounds) +{ + if (TextInputMethodContext.IsValid() && + TextInputMethodContext->CEFCompositionRangeChanged(SelectionRange, CharacterBounds)) + { + if (TextInputMethodChangeNotifier.IsValid()) + { + TextInputMethodChangeNotifier->NotifyLayoutChanged(ITextInputMethodChangeNotifier::ELayoutChangeType::Changed); + } + } +} + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceImeHandler.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceImeHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..224acd897d5861959f4529fc576a69dc23b5d0dc --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceImeHandler.h @@ -0,0 +1,113 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFImeHandler.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 && !PLATFORM_LINUX + +#include "Widgets/SWidget.h" + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/AllowWindowsPlatformAtomics.h" +#endif +#pragma push_macro("OVERRIDE") +#undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +#include "include/cef_client.h" +#include "include/cef_values.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +#pragma pop_macro("OVERRIDE") +#if PLATFORM_WINDOWS +#include "Windows/HideWindowsPlatformAtomics.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif +#include "Layout/Geometry.h" + +class ITextInputMethodSystem; +class FCEFInterfaceTextInputMethodContext; +class ITextInputMethodChangeNotifier; +class SWidget; + +class FCEFInterfaceImeHandler + : public TSharedFromThis<FCEFInterfaceImeHandler> +{ +public: + FCEFInterfaceImeHandler(CefRefPtr<CefBrowser> Browser); + + void UnbindCefBrowser(); + void CacheBrowserSlateInfo(const TSharedRef<SWidget>& Widget); + void SetFocus(bool bHasFocus); + void UpdateCachedGeometry(const FGeometry& AllottedGeometry); + + /** + * Called when the IME composition DOM node has changed. + * + * @param SelectionRange The range of characters that have been selected. + * @param CharacterBounds The bounds of each character in view coordinates. + */ + void CEFCompositionRangeChanged(const CefRange& SelectionRange, const CefRenderHandler::RectList& CharacterBounds); + + /** + * Called when a message was received from the renderer process. + * + * @param Browser The CefBrowser for this window. + * @param SourceProcess The process id of the sender of the message. Currently always PID_RENDERER. + * @param Message The actual message. + * @return true if the message was handled, else false. + */ + bool OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, CefProcessId SourceProcess, CefRefPtr<CefProcessMessage> Message); + + /** + * Sends a message to the renderer process. + * See https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-inter-process-communication-ipc for more information. + * + * @param Message the message to send to the renderer process + */ + void SendProcessMessage(CefRefPtr<CefProcessMessage> Message); + + // FWebImeHandler Interface + + void BindInputMethodSystem(ITextInputMethodSystem* InTextInputMethodSystem); + void UnbindInputMethodSystem(); + + +private: + + bool IsValid() + { + return InternalCefBrowser.get() != nullptr; + } + + void InitContext(); + void DeactivateContext(); + void DestroyContext(); + + /** Message handling helpers */ + bool HandleFocusChangedMessage(CefRefPtr<CefListValue> MessageArguments); + + /** Pointer to the CEF Browser for this window. */ + CefRefPtr<CefBrowser> InternalCefBrowser; + + TWeakPtr<SWidget> InternalBrowserSlateWidget; + + ITextInputMethodSystem* TextInputMethodSystem; + + /** IME context for this browser window. This gets recreated whenever we change focus to an editable input field. */ + TSharedPtr<FCEFInterfaceTextInputMethodContext> TextInputMethodContext; + + /** Notification interface object for IMEs */ + TSharedPtr<ITextInputMethodChangeNotifier> TextInputMethodChangeNotifier; + + // Allow IME context to access functions only it needs. + friend class FCEFInterfaceTextInputMethodContext; +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSScripting.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSScripting.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e7cc62f31dc96ccfe135e0d4319e7351a1f97385 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSScripting.cpp @@ -0,0 +1,368 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFJSScripting.cpp + +#include "CEF/CEFInterfaceJSScripting.h" + +#if WITH_CEF3 + +#include "WebInterfaceJSScripting.h" +#include "WebInterfaceJSFunction.h" +#include "CEFWebInterfaceBrowserWindow.h" +#include "CEFInterfaceJSStructSerializerBackend.h" +#include "CEFInterfaceJSStructDeserializerBackend.h" +#include "StructSerializer.h" +#include "StructDeserializer.h" + + +// Internal utility function(s) +namespace +{ + + template<typename DestContainerType, typename SrcContainerType, typename DestKeyType, typename SrcKeyType> + bool CopyContainerValue(DestContainerType DestContainer, SrcContainerType SrcContainer, DestKeyType DestKey, SrcKeyType SrcKey ) + { + switch (SrcContainer->GetType(SrcKey)) + { + case VTYPE_NULL: + return DestContainer->SetNull(DestKey); + case VTYPE_BOOL: + return DestContainer->SetBool(DestKey, SrcContainer->GetBool(SrcKey)); + case VTYPE_INT: + return DestContainer->SetInt(DestKey, SrcContainer->GetInt(SrcKey)); + case VTYPE_DOUBLE: + return DestContainer->SetDouble(DestKey, SrcContainer->GetDouble(SrcKey)); + case VTYPE_STRING: + return DestContainer->SetString(DestKey, SrcContainer->GetString(SrcKey)); + case VTYPE_BINARY: + return DestContainer->SetBinary(DestKey, SrcContainer->GetBinary(SrcKey)); + case VTYPE_DICTIONARY: + return DestContainer->SetDictionary(DestKey, SrcContainer->GetDictionary(SrcKey)); + case VTYPE_LIST: + return DestContainer->SetList(DestKey, SrcContainer->GetList(SrcKey)); + case VTYPE_INVALID: + default: + return false; + } + + } +} + +CefRefPtr<CefDictionaryValue> FCEFInterfaceJSScripting::ConvertStruct(UStruct* TypeInfo, const void* StructPtr) +{ + FCEFInterfaceJSStructSerializerBackend Backend (SharedThis(this)); + FStructSerializer::Serialize(StructPtr, *TypeInfo, Backend); + + CefRefPtr<CefDictionaryValue> Result = CefDictionaryValue::Create(); + Result->SetString("$type", "struct"); + Result->SetString("$ue4Type", TCHAR_TO_WCHAR(*GetBindingName(TypeInfo))); + Result->SetDictionary("$value", Backend.GetResult()); + return Result; +} + +CefRefPtr<CefDictionaryValue> FCEFInterfaceJSScripting::ConvertObject(UObject* Object) +{ + CefRefPtr<CefDictionaryValue> Result = CefDictionaryValue::Create(); + RetainBinding(Object); + + UClass* Class = Object->GetClass(); + CefRefPtr<CefListValue> MethodNames = CefListValue::Create(); + int32 MethodIndex = 0; + for (TFieldIterator<UFunction> FunctionIt(Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) + { + UFunction* Function = *FunctionIt; + MethodNames->SetString(MethodIndex++, TCHAR_TO_WCHAR(*GetBindingName(Function))); + } + + Result->SetString("$type", "uobject"); + Result->SetString("$id", TCHAR_TO_WCHAR(*PtrToGuid(Object).ToString(EGuidFormats::Digits))); + Result->SetList("$methods", MethodNames); + return Result; +} + + +bool FCEFInterfaceJSScripting::OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, CefProcessId SourceProcess, CefRefPtr<CefProcessMessage> Message) +{ + bool Result = false; + FString MessageName = WCHAR_TO_TCHAR(Message->GetName().ToWString().c_str()); + if (MessageName == TEXT("UE::ExecuteUObjectMethod")) + { + Result = HandleExecuteUObjectMethodMessage(Message->GetArgumentList()); + } + else if (MessageName == TEXT("UE::ReleaseUObject")) + { + Result = HandleReleaseUObjectMessage(Message->GetArgumentList()); + } + return Result; +} + +void FCEFInterfaceJSScripting::SendProcessMessage(CefRefPtr<CefProcessMessage> Message) +{ + if (IsValid() && InternalCefBrowser->GetMainFrame()) + { + InternalCefBrowser->GetMainFrame()->SendProcessMessage(PID_RENDERER, Message); + } +} + +CefRefPtr<CefDictionaryValue> FCEFInterfaceJSScripting::GetPermanentBindings() +{ + CefRefPtr<CefDictionaryValue> Result = CefDictionaryValue::Create(); + + TMap<FString, UObject*> CachedPermanentUObjectsByName = PermanentUObjectsByName; + + for(auto& Entry : CachedPermanentUObjectsByName) + { + Result->SetDictionary(TCHAR_TO_WCHAR(*Entry.Key), ConvertObject(Entry.Value)); + } + return Result; +} + + +void FCEFInterfaceJSScripting::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + const FString ExposedName = GetBindingName(Name, Object); + CefRefPtr<CefDictionaryValue> Converted = ConvertObject(Object); + if (bIsPermanent) + { + // Each object can only have one permanent binding + if (BoundObjects[Object].bIsPermanent) + { + return; + } + // Existing permanent objects must be removed first + if (PermanentUObjectsByName.Contains(ExposedName)) + { + return; + } + BoundObjects[Object]={true, -1}; + PermanentUObjectsByName.Add(ExposedName, Object); + } + + CefRefPtr<CefProcessMessage> SetValueMessage = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("UE::SetValue"))); + CefRefPtr<CefListValue>MessageArguments = SetValueMessage->GetArgumentList(); + CefRefPtr<CefDictionaryValue> Value = CefDictionaryValue::Create(); + Value->SetString("name", TCHAR_TO_WCHAR(*ExposedName)); + Value->SetDictionary("value", Converted); + Value->SetBool("permanent", bIsPermanent); + + MessageArguments->SetDictionary(0, Value); + SendProcessMessage(SetValueMessage); +} + +void FCEFInterfaceJSScripting::UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + const FString ExposedName = GetBindingName(Name, Object); + + if (bIsPermanent) + { + // If overriding an existing permanent object, make it non-permanent + if (PermanentUObjectsByName.Contains(ExposedName) && (Object == nullptr || PermanentUObjectsByName[ExposedName] == Object)) + { + Object = PermanentUObjectsByName.FindAndRemoveChecked(ExposedName); + BoundObjects.Remove(Object); + return; + } + else + { + return; + } + } + + CefRefPtr<CefProcessMessage> DeleteValueMessage = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("UE::DeleteValue"))); + CefRefPtr<CefListValue>MessageArguments = DeleteValueMessage->GetArgumentList(); + CefRefPtr<CefDictionaryValue> Info = CefDictionaryValue::Create(); + Info->SetString("name", TCHAR_TO_WCHAR(*ExposedName)); + Info->SetString("id", TCHAR_TO_WCHAR(*PtrToGuid(Object).ToString(EGuidFormats::Digits))); + Info->SetBool("permanent", bIsPermanent); + + MessageArguments->SetDictionary(0, Info); + SendProcessMessage(DeleteValueMessage); +} + +bool FCEFInterfaceJSScripting::HandleReleaseUObjectMessage(CefRefPtr<CefListValue> MessageArguments) +{ + FGuid ObjectKey; + // Message arguments are Name, Value, bGlobal + if (MessageArguments->GetSize() != 1 || MessageArguments->GetType(0) != VTYPE_STRING) + { + // Wrong message argument types or count + return false; + } + + if (!FGuid::Parse(FString(WCHAR_TO_TCHAR(MessageArguments->GetString(0).ToWString().c_str())), ObjectKey)) + { + // Invalid GUID + return false; + } + + UObject* Object = GuidToPtr(ObjectKey); + if ( Object == nullptr ) + { + // Invalid object + return false; + } + ReleaseBinding(Object); + return true; +} + +bool FCEFInterfaceJSScripting::HandleExecuteUObjectMethodMessage(CefRefPtr<CefListValue> MessageArguments) +{ + FGuid ObjectKey; + // Message arguments are Name, Value, bGlobal + if (MessageArguments->GetSize() != 4 + || MessageArguments->GetType(0) != VTYPE_STRING + || MessageArguments->GetType(1) != VTYPE_STRING + || MessageArguments->GetType(2) != VTYPE_STRING + || MessageArguments->GetType(3) != VTYPE_LIST + ) + { + // Wrong message argument types or count + return false; + } + + if (!FGuid::Parse(FString(WCHAR_TO_TCHAR(MessageArguments->GetString(0).ToWString().c_str())), ObjectKey)) + { + // Invalid GUID + return false; + } + + // Get the promise callback and use that to report any results from executing this function. + FGuid ResultCallbackId; + if (!FGuid::Parse(FString(WCHAR_TO_TCHAR(MessageArguments->GetString(2).ToWString().c_str())), ResultCallbackId)) + { + // Invalid GUID + return false; + } + + UObject* Object = GuidToPtr(ObjectKey); + if (Object == nullptr) + { + // Unknown uobject id + InvokeJSErrorResult(ResultCallbackId, TEXT("Unknown UObject ID")); + return true; + } + + FName MethodName = WCHAR_TO_TCHAR(MessageArguments->GetString(1).ToWString().c_str()); + UFunction* Function = Object->FindFunction(MethodName); + if (!Function) + { + InvokeJSErrorResult(ResultCallbackId, TEXT("Unknown UObject Function")); + return true; + } + // Coerce arguments to function arguments. + uint16 ParamsSize = Function->ParmsSize; + uint8* Params = nullptr; + FProperty* ReturnParam = nullptr; + FProperty* PromiseParam = nullptr; + + if (ParamsSize > 0) + { + // Convert cef argument list to a dictionary, so we can use FStructDeserializer to convert it for us + CefRefPtr<CefDictionaryValue> NamedArgs = CefDictionaryValue::Create(); + int32 CurrentArg = 0; + CefRefPtr<CefListValue> CefArgs = MessageArguments->GetList(3); + for ( TFieldIterator<FProperty> It(Function); It; ++It ) + { + FProperty* Param = *It; + if (Param->PropertyFlags & CPF_Parm) + { + if (Param->PropertyFlags & CPF_ReturnParm) + { + ReturnParam = Param; + } + else + { + FStructProperty *StructProperty = CastField<FStructProperty>(Param); + if (StructProperty && StructProperty->Struct->IsChildOf(FWebInterfaceJSResponse::StaticStruct())) + { + PromiseParam = Param; + } + else + { + CopyContainerValue(NamedArgs, CefArgs, CefString(TCHAR_TO_WCHAR(*GetBindingName(Param))), CurrentArg); + CurrentArg++; + } + } + } + } + + // UFunction is a subclass of UStruct, so we can treat the arguments as a struct for deserialization + check(nullptr == Params); + Params = (uint8*)FMemory::Malloc(Function->GetStructureSize()); + Function->InitializeStruct(Params); + FCEFInterfaceJSStructDeserializerBackend Backend = FCEFInterfaceJSStructDeserializerBackend(SharedThis(this), NamedArgs); + FStructDeserializer::Deserialize(Params, *Function, Backend); + } + + if (PromiseParam) + { + FWebInterfaceJSResponse* PromisePtr = PromiseParam->ContainerPtrToValuePtr<FWebInterfaceJSResponse>(Params); + if (PromisePtr) + { + *PromisePtr = FWebInterfaceJSResponse(SharedThis(this), ResultCallbackId); + } + } + + Object->ProcessEvent(Function, Params); + CefRefPtr<CefListValue> Results = CefListValue::Create(); + + if ( ! PromiseParam ) // If PromiseParam is set, we assume that the UFunction will ensure it is called with the result + { + if ( ReturnParam ) + { + FStructSerializerPolicies ReturnPolicies; + ReturnPolicies.PropertyFilter = [&](const FProperty* CandidateProperty, const FProperty* ParentProperty) + { + return ParentProperty != nullptr || CandidateProperty == ReturnParam; + }; + FCEFInterfaceJSStructSerializerBackend ReturnBackend(SharedThis(this)); + FStructSerializer::Serialize(Params, *Function, ReturnBackend, ReturnPolicies); + CefRefPtr<CefDictionaryValue> ResultDict = ReturnBackend.GetResult(); + + // Extract the single return value from the serialized dictionary to an array + CopyContainerValue(Results, ResultDict, 0, TCHAR_TO_WCHAR(*GetBindingName(ReturnParam))); + } + InvokeJSFunction(ResultCallbackId, Results, false); + } + + if (Params) + { + Function->DestroyStruct(Params); + FMemory::Free(Params); + Params = nullptr; + } + + return true; +} + +void FCEFInterfaceJSScripting::UnbindCefBrowser() +{ + InternalCefBrowser = nullptr; +} + +void FCEFInterfaceJSScripting::InvokeJSErrorResult(FGuid FunctionId, const FString& Error) +{ + CefRefPtr<CefListValue> FunctionArguments = CefListValue::Create(); + FunctionArguments->SetString(0, TCHAR_TO_WCHAR(*Error)); + InvokeJSFunction(FunctionId, FunctionArguments, true); +} + +void FCEFInterfaceJSScripting::InvokeJSFunction(FGuid FunctionId, int32 ArgCount, FWebInterfaceJSParam Arguments[], bool bIsError) +{ + CefRefPtr<CefListValue> FunctionArguments = CefListValue::Create(); + for ( int32 i=0; i<ArgCount; i++) + { + SetConverted(FunctionArguments, i, Arguments[i]); + } + InvokeJSFunction(FunctionId, FunctionArguments, bIsError); +} + +void FCEFInterfaceJSScripting::InvokeJSFunction(FGuid FunctionId, const CefRefPtr<CefListValue>& FunctionArguments, bool bIsError) +{ + CefRefPtr<CefProcessMessage> Message = CefProcessMessage::Create(TCHAR_TO_WCHAR(TEXT("UE::ExecuteJSFunction"))); + CefRefPtr<CefListValue> MessageArguments = Message->GetArgumentList(); + MessageArguments->SetString(0, TCHAR_TO_WCHAR(*FunctionId.ToString(EGuidFormats::Digits))); + MessageArguments->SetList(1, FunctionArguments); + MessageArguments->SetBool(2, bIsError); + SendProcessMessage(Message); +} + +#endif \ No newline at end of file diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSScripting.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSScripting.h new file mode 100644 index 0000000000000000000000000000000000000000..d314844141cba1bf31b0d6024a4585b597004de3 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSScripting.h @@ -0,0 +1,161 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFJSScripting.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 +#include "WebInterfaceJSFunction.h" +#include "WebInterfaceJSScripting.h" + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/AllowWindowsPlatformAtomics.h" +#endif +#pragma push_macro("OVERRIDE") +#undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +#include "include/cef_client.h" +#include "include/cef_values.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +#pragma pop_macro("OVERRIDE") +#if PLATFORM_WINDOWS +#include "Windows/HideWindowsPlatformAtomics.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif +#endif + +class Error; +class FWebInterfaceJSScripting; +struct FWebInterfaceJSParam; + +#if WITH_CEF3 + +/** + * Implements handling of bridging UObjects client side with JavaScript renderer side. + */ +class FCEFInterfaceJSScripting + : public FWebInterfaceJSScripting + , public TSharedFromThis<FCEFInterfaceJSScripting> +{ +public: + FCEFInterfaceJSScripting(CefRefPtr<CefBrowser> Browser, bool bJSBindingToLoweringEnabled) + : FWebInterfaceJSScripting(bJSBindingToLoweringEnabled) + , InternalCefBrowser(Browser) + {} + + void UnbindCefBrowser(); + + virtual void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) override; + virtual void UnbindUObject(const FString& Name, UObject* Object = nullptr, bool bIsPermanent = true) override; + + /** + * Called when a message was received from the renderer process. + * + * @param Browser The CefBrowser for this window. + * @param SourceProcess The process id of the sender of the message. Currently always PID_RENDERER. + * @param Message The actual message. + * @return true if the message was handled, else false. + */ + bool OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, CefProcessId SourceProcess, CefRefPtr<CefProcessMessage> Message); + + /** + * Sends a message to the renderer process. + * See https://bitbucket.org/chromiumembedded/cef/wiki/GeneralUsage#markdown-header-inter-process-communication-ipc for more information. + * + * @param Message the message to send to the renderer process + */ + void SendProcessMessage(CefRefPtr<CefProcessMessage> Message); + + CefRefPtr<CefDictionaryValue> ConvertStruct(UStruct* TypeInfo, const void* StructPtr); + CefRefPtr<CefDictionaryValue> ConvertObject(UObject* Object); + + // Works for CefListValue and CefDictionaryValues + template<typename ContainerType, typename KeyType> + bool SetConverted(CefRefPtr<ContainerType> Container, KeyType Key, FWebInterfaceJSParam& Param) + { + switch (Param.Tag) + { + case FWebInterfaceJSParam::PTYPE_NULL: + return Container->SetNull(Key); + case FWebInterfaceJSParam::PTYPE_BOOL: + return Container->SetBool(Key, Param.BoolValue); + case FWebInterfaceJSParam::PTYPE_DOUBLE: + return Container->SetDouble(Key, Param.DoubleValue); + case FWebInterfaceJSParam::PTYPE_INT: + return Container->SetInt(Key, Param.IntValue); + case FWebInterfaceJSParam::PTYPE_STRING: + { + CefString ConvertedString = TCHAR_TO_WCHAR(**Param.StringValue); + return Container->SetString(Key, ConvertedString); + } + case FWebInterfaceJSParam::PTYPE_OBJECT: + { + if (Param.ObjectValue == nullptr) + { + return Container->SetNull(Key); + } + else + { + CefRefPtr<CefDictionaryValue> ConvertedObject = ConvertObject(Param.ObjectValue); + return Container->SetDictionary(Key, ConvertedObject); + } + } + case FWebInterfaceJSParam::PTYPE_STRUCT: + { + CefRefPtr<CefDictionaryValue> ConvertedStruct = ConvertStruct(Param.StructValue->GetTypeInfo(), Param.StructValue->GetData()); + return Container->SetDictionary(Key, ConvertedStruct); + } + case FWebInterfaceJSParam::PTYPE_ARRAY: + { + CefRefPtr<CefListValue> ConvertedArray = CefListValue::Create(); + for(int i=0; i < Param.ArrayValue->Num(); ++i) + { + SetConverted(ConvertedArray, i, (*Param.ArrayValue)[i]); + } + return Container->SetList(Key, ConvertedArray); + } + case FWebInterfaceJSParam::PTYPE_MAP: + { + CefRefPtr<CefDictionaryValue> ConvertedMap = CefDictionaryValue::Create(); + for(auto& Pair : *Param.MapValue) + { + SetConverted(ConvertedMap, TCHAR_TO_WCHAR(*Pair.Key), Pair.Value); + } + return Container->SetDictionary(Key, ConvertedMap); + } + default: + return false; + } + } + + CefRefPtr<CefDictionaryValue> GetPermanentBindings(); + + void InvokeJSFunction(FGuid FunctionId, int32 ArgCount, FWebInterfaceJSParam Arguments[], bool bIsError=false) override; + void InvokeJSFunction(FGuid FunctionId, const CefRefPtr<CefListValue>& FunctionArguments, bool bIsError=false); + void InvokeJSErrorResult(FGuid FunctionId, const FString& Error) override; + +private: + bool ConvertStructArgImpl(uint8* Args, FProperty* Param, CefRefPtr<CefListValue> List, int32 Index); + + bool IsValid() + { + return InternalCefBrowser.get() != nullptr; + } + + /** Message handling helpers */ + + bool HandleExecuteUObjectMethodMessage(CefRefPtr<CefListValue> MessageArguments); + bool HandleReleaseUObjectMessage(CefRefPtr<CefListValue> MessageArguments); + + /** Pointer to the CEF Browser for this window. */ + CefRefPtr<CefBrowser> InternalCefBrowser; +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructDeserializerBackend.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructDeserializerBackend.cpp new file mode 100644 index 0000000000000000000000000000000000000000..94c45fb61e2a4c746fdd6943a8fe0ff5317dbb9e --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructDeserializerBackend.cpp @@ -0,0 +1,395 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFJSStructDeserializerBackend.cpp + +#include "CEF/CEFInterfaceJSStructDeserializerBackend.h" +#if WITH_CEF3 +#include "UObject/EnumProperty.h" +#include "UObject/TextProperty.h" +#include "WebInterfaceJSFunction.h" + +/* Internal helpers + *****************************************************************************/ +namespace { + + template<typename ValueType, typename ContainerType, typename KeyType> + ValueType GetNumeric(CefRefPtr<ContainerType> Container, KeyType Key) + { + switch(Container->GetType(Key)) + { + case VTYPE_BOOL: + return static_cast<ValueType>(Container->GetBool(Key)); + case VTYPE_INT: + return static_cast<ValueType>(Container->GetInt(Key)); + case VTYPE_DOUBLE: + return static_cast<ValueType>(Container->GetDouble(Key)); + case VTYPE_STRING: + case VTYPE_DICTIONARY: + case VTYPE_LIST: + case VTYPE_NULL: + case VTYPE_BINARY: + default: + return static_cast<ValueType>(0); + } + } + + template<typename ContainerType, typename KeyType> + void AssignTokenFromContainer(ContainerType Container, KeyType Key, EStructDeserializerBackendTokens& OutToken, FString& PropertyName, TSharedPtr<ICefInterfaceContainerWalker>& Retval) + { + switch (Container->GetType(Key)) + { + case VTYPE_NULL: + case VTYPE_BOOL: + case VTYPE_INT: + case VTYPE_DOUBLE: + case VTYPE_STRING: + OutToken = EStructDeserializerBackendTokens::Property; + break; + case VTYPE_DICTIONARY: + { + CefRefPtr<CefDictionaryValue> Dictionary = Container->GetDictionary(Key); + if (Dictionary->GetType("$type") == VTYPE_STRING ) + { + OutToken = EStructDeserializerBackendTokens::Property; + } + else + { + TSharedPtr<ICefInterfaceContainerWalker> NewWalker(new FCefInterfaceDictionaryValueWalker(Retval, Dictionary)); + Retval = NewWalker->GetNextToken(OutToken, PropertyName); + } + break; + } + case VTYPE_LIST: + { + TSharedPtr<ICefInterfaceContainerWalker> NewWalker(new FCefInterfaceListValueWalker(Retval, Container->GetList(Key))); + Retval = NewWalker->GetNextToken(OutToken, PropertyName); + break; + } + case VTYPE_BINARY: + case VTYPE_INVALID: + default: + OutToken = EStructDeserializerBackendTokens::Error; + break; + } + } + + /** + * Gets a pointer to object of the given property. + * + * @param Property The property to get. + * @param Outer The property that contains the property to be get, if any. + * @param Data A pointer to the memory holding the property's data. + * @param ArrayIndex The index of the element to set (if the property is an array). + * @return A pointer to the object represented by the property, null otherwise.. + * @see ClearPropertyValue + */ + void* GetPropertyValuePtr( FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex ) + { + check(Property); + + if (FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Outer)) + { + if (ArrayProperty->Inner != Property) + { + return nullptr; + } + + FScriptArrayHelper ArrayHelper(ArrayProperty, ArrayProperty->template ContainerPtrToValuePtr<void>(Data)); + int32 Index = ArrayHelper.AddValue(); + + return ArrayHelper.GetRawPtr(Index); + } + + if (ArrayIndex >= Property->ArrayDim) + { + return nullptr; + } + + return Property->template ContainerPtrToValuePtr<void>(Data, ArrayIndex); + } + + /** + * Sets the value of the given property. + * + * @param Property The property to set. + * @param Outer The property that contains the property to be set, if any. + * @param Data A pointer to the memory holding the property's data. + * @param ArrayIndex The index of the element to set (if the property is an array). + * @return true on success, false otherwise. + * @see ClearPropertyValue + */ + template<typename PropertyType, typename ValueType> + bool SetPropertyValue( PropertyType* Property, FProperty* Outer, void* Data, int32 ArrayIndex, const ValueType& Value ) + { + if (void* Ptr = GetPropertyValuePtr(Property, Outer, Data, ArrayIndex)) + { + *(ValueType*)Ptr = Value; + return true; + } + + return false; + } + + template<typename PropertyType, typename ContainerType, typename KeyType> + bool ReadNumericProperty(FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex, CefRefPtr<ContainerType> Container, KeyType Key ) + { + typedef typename PropertyType::TCppType TCppType; + if (PropertyType* TypedProperty = CastField<PropertyType>(Property)) + { + return SetPropertyValue(TypedProperty, Outer, Data, ArrayIndex, GetNumeric<TCppType>(Container, Key)); + } + else + { + return false; + } + } + + template<typename ContainerType, typename KeyType> + bool ReadBoolProperty(FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex, CefRefPtr<ContainerType> Container, KeyType Key ) + { + if (FBoolProperty* BoolProperty = CastField<FBoolProperty>(Property)) + { + return SetPropertyValue(BoolProperty, Outer, Data, ArrayIndex, GetNumeric<int>(Container, Key)!=0); + } + return false; + + } + + template<typename ContainerType, typename KeyType> + bool ReadJSFunctionProperty(TSharedPtr<FCEFInterfaceJSScripting> Scripting, FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex, CefRefPtr<ContainerType> Container, KeyType Key ) + { + if (Container->GetType(Key) != VTYPE_DICTIONARY || !Property->IsA<FStructProperty>()) + { + return false; + } + CefRefPtr<CefDictionaryValue> Dictionary = Container->GetDictionary(Key); + FStructProperty* StructProperty = CastField<FStructProperty>(Property); + + if ( !StructProperty || StructProperty->Struct != FWebInterfaceJSFunction::StaticStruct()) + { + return false; + } + + FGuid CallbackID; + if (!FGuid::Parse(FString(WCHAR_TO_TCHAR(Dictionary->GetString("$id").ToWString().c_str())), CallbackID)) + { + // Invalid GUID + return false; + } + FWebInterfaceJSFunction CallbackObject(Scripting, CallbackID); + return SetPropertyValue(StructProperty, Outer, Data, ArrayIndex, CallbackObject); + } + + template<typename ContainerType, typename KeyType> + bool ReadStringProperty(FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex, CefRefPtr<ContainerType> Container, KeyType Key ) + { + if (Container->GetType(Key) == VTYPE_STRING) + { + FString StringValue = WCHAR_TO_TCHAR(Container->GetString(Key).ToWString().c_str()); + + if (FStrProperty* StrProperty = CastField<FStrProperty>(Property)) + { + return SetPropertyValue(StrProperty, Outer, Data, ArrayIndex, StringValue); + } + + if (FNameProperty* NameProperty = CastField<FNameProperty>(Property)) + { + return SetPropertyValue(NameProperty, Outer, Data, ArrayIndex, FName(*StringValue)); + } + + if (FTextProperty* TextProperty = CastField<FTextProperty>(Property)) + { + return SetPropertyValue(TextProperty, Outer, Data, ArrayIndex, FText::FromString(StringValue)); + } + + if (FByteProperty* ByteProperty = CastField<FByteProperty>(Property)) + { + if (!ByteProperty->Enum) + { + return false; + } + + int32 Index = ByteProperty->Enum->GetIndexByNameString(StringValue); + if (Index == INDEX_NONE) + { + return false; + } + + return SetPropertyValue(ByteProperty, Outer, Data, ArrayIndex, (uint8)ByteProperty->Enum->GetValueByIndex(Index)); + } + + if (FEnumProperty* EnumProperty = CastField<FEnumProperty>(Property)) + { + int32 Index = EnumProperty->GetEnum()->GetIndexByNameString(StringValue); + if (Index == INDEX_NONE) + { + return false; + } + + if (void* ElementPtr = GetPropertyValuePtr(EnumProperty, Outer, Data, ArrayIndex)) + { + EnumProperty->GetUnderlyingProperty()->SetIntPropertyValue(ElementPtr, EnumProperty->GetEnum()->GetValueByIndex(Index)); + return true; + } + + return false; + } + } + + return false; + } + + template<typename ContainerType, typename KeyType> + bool ReadProperty(TSharedPtr<FCEFInterfaceJSScripting> Scripting, FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex, CefRefPtr<ContainerType> Container, KeyType Key ) + { + return ReadBoolProperty(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadStringProperty(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FByteProperty>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FInt8Property>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FInt16Property>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FIntProperty>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FInt64Property>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FUInt16Property>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FUInt32Property>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FUInt64Property>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FFloatProperty>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadNumericProperty<FDoubleProperty>(Property, Outer, Data, ArrayIndex, Container, Key) + || ReadJSFunctionProperty(Scripting, Property, Outer, Data, ArrayIndex, Container, Key); + } +} + +TSharedPtr<ICefInterfaceContainerWalker> FCefInterfaceListValueWalker::GetNextToken(EStructDeserializerBackendTokens& OutToken, FString& PropertyName) +{ + TSharedPtr<ICefInterfaceContainerWalker> Retval = SharedThis(this); + Index++; + if (Index == -1) + { + OutToken = EStructDeserializerBackendTokens::ArrayStart; + } + else if ( Index < List->GetSize() ) + { + AssignTokenFromContainer(List, Index, OutToken, PropertyName, Retval); + PropertyName = FString(); + } + else + { + OutToken = EStructDeserializerBackendTokens::ArrayEnd; + Retval = Parent; + } + return Retval; +} + +bool FCefInterfaceListValueWalker::ReadProperty(TSharedPtr<FCEFInterfaceJSScripting> Scripting, FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex) +{ + return ::ReadProperty(Scripting, Property, Outer, Data, ArrayIndex, List, Index); +} + +TSharedPtr<ICefInterfaceContainerWalker> FCefInterfaceDictionaryValueWalker::GetNextToken(EStructDeserializerBackendTokens& OutToken, FString& PropertyName) +{ + TSharedPtr<ICefInterfaceContainerWalker> Retval = SharedThis(this); + Index++; + if (Index == -1) + { + OutToken = EStructDeserializerBackendTokens::StructureStart; + } + else if ( Index < Keys.size() ) + { + AssignTokenFromContainer(Dictionary, Keys[Index], OutToken, PropertyName, Retval); + PropertyName = WCHAR_TO_TCHAR(Keys[Index].ToWString().c_str()); + } + else + { + OutToken = EStructDeserializerBackendTokens::StructureEnd; + Retval = Parent; + } + return Retval; +} + +bool FCefInterfaceDictionaryValueWalker::ReadProperty(TSharedPtr<FCEFInterfaceJSScripting> Scripting, FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex) +{ + return ::ReadProperty(Scripting, Property, Outer, Data, ArrayIndex, Dictionary, Keys[Index]); +} + + +/* IStructDeserializerBackend interface + *****************************************************************************/ + + + +const FString& FCEFInterfaceJSStructDeserializerBackend::GetCurrentPropertyName() const +{ + return CurrentPropertyName; +} + + +FString FCEFInterfaceJSStructDeserializerBackend::GetDebugString() const +{ + return CurrentPropertyName; +} + + +const FString& FCEFInterfaceJSStructDeserializerBackend::GetLastErrorMessage() const +{ + return CurrentPropertyName; +} + + +bool FCEFInterfaceJSStructDeserializerBackend::GetNextToken( EStructDeserializerBackendTokens& OutToken ) +{ + if (Walker.IsValid()) + { + Walker = Walker->GetNextToken(OutToken, CurrentPropertyName); + return true; + } + else + { + return false; + } +} + + +bool FCEFInterfaceJSStructDeserializerBackend::ReadProperty( FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex ) +{ + return Walker->ReadProperty(Scripting, Property, Outer, Data, ArrayIndex); +} + + +void FCEFInterfaceJSStructDeserializerBackend::SkipArray() +{ + EStructDeserializerBackendTokens Token; + int32 depth = 1; + while (GetNextToken(Token) && depth > 0) + { + switch (Token) + { + case EStructDeserializerBackendTokens::ArrayEnd: + depth --; + break; + case EStructDeserializerBackendTokens::ArrayStart: + depth ++; + break; + default: + break; + } + } +} + +void FCEFInterfaceJSStructDeserializerBackend::SkipStructure() +{ + EStructDeserializerBackendTokens Token; + int32 depth = 1; + while (GetNextToken(Token) && depth > 0) + { + switch (Token) + { + case EStructDeserializerBackendTokens::StructureEnd: + depth --; + break; + case EStructDeserializerBackendTokens::StructureStart: + depth ++; + break; + default: + break; + } + } +} + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructDeserializerBackend.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructDeserializerBackend.h new file mode 100644 index 0000000000000000000000000000000000000000..b735b57a219a676ce85ac890eefc4b1dfc0006ac --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructDeserializerBackend.h @@ -0,0 +1,136 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFJSStructDeserializerBackend.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 + +#include "WebInterfaceBrowserSingleton.h" +#include "UObject/UnrealType.h" +#include "IStructDeserializerBackend.h" +#include "CEFInterfaceJSScripting.h" + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/AllowWindowsPlatformAtomics.h" +#endif +#pragma push_macro("OVERRIDE") +#undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +#include "include/cef_values.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +#pragma pop_macro("OVERRIDE") +#if PLATFORM_WINDOWS +#include "Windows/HideWindowsPlatformAtomics.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif + +#endif + +class FCEFInterfaceJSScripting; +class IStructDeserializerBackend; +enum class EStructDeserializerBackendTokens; +class FProperty; +class UStruct; + +#if WITH_CEF3 + +class ICefInterfaceContainerWalker + : public TSharedFromThis<ICefInterfaceContainerWalker> +{ +public: + ICefInterfaceContainerWalker(TSharedPtr<ICefInterfaceContainerWalker> InParent) + : Parent(InParent) + {} + virtual ~ICefInterfaceContainerWalker() {} + + virtual TSharedPtr<ICefInterfaceContainerWalker> GetNextToken( EStructDeserializerBackendTokens& OutToken, FString& PropertyName ) = 0; + virtual bool ReadProperty(TSharedPtr<FCEFInterfaceJSScripting> Scripting, FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex) = 0; + + TSharedPtr<ICefInterfaceContainerWalker> Parent; +}; + +class FCefInterfaceListValueWalker + : public ICefInterfaceContainerWalker +{ +public: + FCefInterfaceListValueWalker(TSharedPtr<ICefInterfaceContainerWalker> InParent, CefRefPtr<CefListValue> InList) + : ICefInterfaceContainerWalker(InParent) + , List(InList) + , Index(-2) + {} + + virtual TSharedPtr<ICefInterfaceContainerWalker> GetNextToken( EStructDeserializerBackendTokens& OutToken, FString& PropertyName ) override; + virtual bool ReadProperty(TSharedPtr<FCEFInterfaceJSScripting> Scripting, FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex ) override; + + CefRefPtr<CefListValue> List; + size_t Index; +}; + +class FCefInterfaceDictionaryValueWalker + : public ICefInterfaceContainerWalker +{ +public: + FCefInterfaceDictionaryValueWalker(TSharedPtr<ICefInterfaceContainerWalker> InParent, CefRefPtr<CefDictionaryValue> InDictionary) + : ICefInterfaceContainerWalker(InParent) + , Dictionary(InDictionary) + , Index(-2) + { + Dictionary->GetKeys(Keys); + } + + virtual TSharedPtr<ICefInterfaceContainerWalker> GetNextToken( EStructDeserializerBackendTokens& OutToken, FString& PropertyName ) override; + virtual bool ReadProperty(TSharedPtr<FCEFInterfaceJSScripting> Scripting, FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex ) override; + +private: + CefRefPtr<CefDictionaryValue> Dictionary; + size_t Index; + CefDictionaryValue::KeyList Keys; +}; + +/** + * Implements a writer for UStruct serialization using CefDictionary. + */ +class FCEFInterfaceJSStructDeserializerBackend + : public IStructDeserializerBackend +{ +public: + + /** + * Creates and initializes a new instance. + * + * @param Archive The archive to deserialize from. + */ + FCEFInterfaceJSStructDeserializerBackend(TSharedPtr<FCEFInterfaceJSScripting> InScripting, CefRefPtr<CefDictionaryValue> InDictionary) + : Scripting(InScripting) + , Walker(new FCefInterfaceDictionaryValueWalker(nullptr, InDictionary)) + , CurrentPropertyName() + { } + +public: + + // IStructDeserializerBackend interface + + virtual const FString& GetCurrentPropertyName() const override; + virtual FString GetDebugString() const override; + virtual const FString& GetLastErrorMessage() const override; + virtual bool GetNextToken( EStructDeserializerBackendTokens& OutToken ) override; + virtual bool ReadProperty( FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex ) override; + virtual void SkipArray() override; + virtual void SkipStructure() override; + +private: + TSharedPtr<FCEFInterfaceJSScripting> Scripting; + /** Holds the source CEF dictionary containing a serialized verion of the structure. */ + TSharedPtr<ICefInterfaceContainerWalker> Walker; + FString CurrentPropertyName; +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructSerializerBackend.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructSerializerBackend.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7275cabb0b64da6a0ba0387768ea5b5d1a18bfcf --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructSerializerBackend.cpp @@ -0,0 +1,298 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFJSStructSerializerBackend.cpp + +#include "CEF/CEFInterfaceJSStructSerializerBackend.h" +#if WITH_CEF3 + +#include "UObject/EnumProperty.h" +#include "UObject/TextProperty.h" +#include "UObject/PropertyPortFlags.h" +#include "Misc/CommandLine.h" + +static FString GetBindingName(const TSharedPtr<FCEFInterfaceJSScripting>& Scripting, const FProperty* ValueProperty) +{ + //@todo samz - HACK + static const bool bIsKairos = FParse::Param(FCommandLine::Get(), TEXT("KairosOnly")); + if (bIsKairos) + { + // skip lowercasing property field names for compatibility with FNativeJSStructSerializerBackend/FMobileJSStructSerializerBackend + return ValueProperty->GetName(); + } + else + { + return Scripting->GetBindingName(ValueProperty); + } +} + +/* Private methods + *****************************************************************************/ + +void FCEFInterfaceJSStructSerializerBackend::AddNull(const FStructSerializerState& State) +{ + StackItem& Current = Stack.Top(); + switch (Current.Kind) { + case StackItem::STYPE_DICTIONARY: + Current.DictionaryValue->SetNull(TCHAR_TO_WCHAR(*GetBindingName(Scripting, State.ValueProperty))); + break; + case StackItem::STYPE_LIST: + Current.ListValue->SetNull(Current.ListValue->GetSize()); + break; + } +} + + +void FCEFInterfaceJSStructSerializerBackend::Add(const FStructSerializerState& State, bool Value) +{ + StackItem& Current = Stack.Top(); + switch (Current.Kind) { + case StackItem::STYPE_DICTIONARY: + Current.DictionaryValue->SetBool(TCHAR_TO_WCHAR(*GetBindingName(Scripting, State.ValueProperty)), Value); + break; + case StackItem::STYPE_LIST: + Current.ListValue->SetBool(Current.ListValue->GetSize(), Value); + break; + } +} + + +void FCEFInterfaceJSStructSerializerBackend::Add(const FStructSerializerState& State, int32 Value) +{ + StackItem& Current = Stack.Top(); + switch (Current.Kind) { + case StackItem::STYPE_DICTIONARY: + Current.DictionaryValue->SetInt(TCHAR_TO_WCHAR(*GetBindingName(Scripting, State.ValueProperty)), Value); + break; + case StackItem::STYPE_LIST: + Current.ListValue->SetInt(Current.ListValue->GetSize(), Value); + break; + } +} + + +void FCEFInterfaceJSStructSerializerBackend::Add(const FStructSerializerState& State, double Value) +{ + StackItem& Current = Stack.Top(); + switch (Current.Kind) { + case StackItem::STYPE_DICTIONARY: + Current.DictionaryValue->SetDouble(TCHAR_TO_WCHAR(*GetBindingName(Scripting, State.ValueProperty)), Value); + break; + case StackItem::STYPE_LIST: + Current.ListValue->SetDouble(Current.ListValue->GetSize(), Value); + break; + } +} + + +void FCEFInterfaceJSStructSerializerBackend::Add(const FStructSerializerState& State, FString Value) +{ + StackItem& Current = Stack.Top(); + switch (Current.Kind) { + case StackItem::STYPE_DICTIONARY: + Current.DictionaryValue->SetString(TCHAR_TO_WCHAR(*GetBindingName(Scripting, State.ValueProperty)), TCHAR_TO_WCHAR(*Value)); + break; + case StackItem::STYPE_LIST: + Current.ListValue->SetString(Current.ListValue->GetSize(), TCHAR_TO_WCHAR(*Value)); + break; + } +} + + +void FCEFInterfaceJSStructSerializerBackend::Add(const FStructSerializerState& State, UObject* Value) +{ + StackItem& Current = Stack.Top(); + switch (Current.Kind) { + case StackItem::STYPE_DICTIONARY: + Current.DictionaryValue->SetDictionary(TCHAR_TO_WCHAR(*GetBindingName(Scripting, State.ValueProperty)), Scripting->ConvertObject(Value)); + break; + case StackItem::STYPE_LIST: + Current.ListValue->SetDictionary(Current.ListValue->GetSize(), Scripting->ConvertObject(Value)); + break; + } +} + + +/* IStructSerializerBackend interface + *****************************************************************************/ + +void FCEFInterfaceJSStructSerializerBackend::BeginArray(const FStructSerializerState& State) +{ + CefRefPtr<CefListValue> ListValue = CefListValue::Create(); + Stack.Push(StackItem(GetBindingName(Scripting, State.ValueProperty), ListValue)); +} + + +void FCEFInterfaceJSStructSerializerBackend::BeginStructure(const FStructSerializerState& State) +{ + if (State.KeyProperty != nullptr) + { + FString KeyString; + State.KeyProperty->ExportTextItem_Direct(KeyString, State.KeyData, nullptr, nullptr, PPF_None); + + CefRefPtr<CefDictionaryValue> DictionaryValue = CefDictionaryValue::Create(); + Stack.Push(StackItem(KeyString, DictionaryValue)); + } + else if (State.ValueProperty != nullptr) + { + CefRefPtr<CefDictionaryValue> DictionaryValue = CefDictionaryValue::Create(); + Stack.Push(StackItem(GetBindingName(Scripting, State.ValueProperty), DictionaryValue)); + } + else + { + Result = CefDictionaryValue::Create(); + Stack.Push(StackItem(FString(), Result)); + } +} + + +void FCEFInterfaceJSStructSerializerBackend::EndArray(const FStructSerializerState& /*State*/) +{ + StackItem Previous = Stack.Pop(); + check(Previous.Kind == StackItem::STYPE_LIST); + check(Stack.Num() > 0); // The root level object is always a struct + StackItem& Current = Stack.Top(); + + switch (Current.Kind) { + case StackItem::STYPE_DICTIONARY: + Current.DictionaryValue->SetList(TCHAR_TO_WCHAR(*Previous.Name), Previous.ListValue); + break; + case StackItem::STYPE_LIST: + Current.ListValue->SetList(Current.ListValue->GetSize(), Previous.ListValue); + break; + } +} + + +void FCEFInterfaceJSStructSerializerBackend::EndStructure(const FStructSerializerState& /*State*/) +{ + StackItem Previous = Stack.Pop(); + check(Previous.Kind == StackItem::STYPE_DICTIONARY); + + if (Stack.Num() > 0) + { + StackItem& Current = Stack.Top(); + + switch (Current.Kind) { + case StackItem::STYPE_DICTIONARY: + Current.DictionaryValue->SetDictionary(TCHAR_TO_WCHAR(*Previous.Name), Previous.DictionaryValue); + break; + case StackItem::STYPE_LIST: + Current.ListValue->SetDictionary(Current.ListValue->GetSize(), Previous.DictionaryValue); + break; + } + } + else + { + check(Result == Previous.DictionaryValue); + } +} + + +void FCEFInterfaceJSStructSerializerBackend::WriteComment(const FString& Comment) +{ + // Cef values do not support comments +} + + +void FCEFInterfaceJSStructSerializerBackend::WriteProperty(const FStructSerializerState& State, int32 ArrayIndex) +{ + // booleans + if (State.FieldType == FBoolProperty::StaticClass()) + { + Add(State, CastFieldChecked<FBoolProperty>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + + // unsigned bytes & enumerations + else if (State.FieldType == FEnumProperty::StaticClass()) + { + FEnumProperty* EnumProperty = CastFieldChecked<FEnumProperty>(State.ValueProperty); + + Add(State, EnumProperty->GetEnum()->GetNameStringByValue(EnumProperty->GetUnderlyingProperty()->GetSignedIntPropertyValue(EnumProperty->ContainerPtrToValuePtr<void>(State.ValueData, ArrayIndex)))); + } + else if (State.FieldType == FByteProperty::StaticClass()) + { + FByteProperty* ByteProperty = CastFieldChecked<FByteProperty>(State.ValueProperty); + + if (ByteProperty->IsEnum()) + { + Add(State, ByteProperty->Enum->GetNameStringByValue(ByteProperty->GetPropertyValue_InContainer(State.ValueData, ArrayIndex))); + } + else + { + Add(State, (double)ByteProperty->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + } + + // floating point numbers + else if (State.FieldType == FDoubleProperty::StaticClass()) + { + Add(State, CastFieldChecked<FDoubleProperty>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + else if (State.FieldType == FFloatProperty::StaticClass()) + { + Add(State, CastFieldChecked<FFloatProperty>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + + // signed integers + else if (State.FieldType == FIntProperty::StaticClass()) + { + Add(State, (int32)CastFieldChecked<FIntProperty>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + else if (State.FieldType == FInt8Property::StaticClass()) + { + Add(State, (int32)CastFieldChecked<FInt8Property>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + else if (State.FieldType == FInt16Property::StaticClass()) + { + Add(State, (int32)CastFieldChecked<FInt16Property>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + else if (State.FieldType == FInt64Property::StaticClass()) + { + Add(State, (double)CastFieldChecked<FInt64Property>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + + // unsigned integers + else if (State.FieldType == FUInt16Property::StaticClass()) + { + Add(State, (int32)CastFieldChecked<FUInt16Property>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + else if (State.FieldType == FUInt32Property::StaticClass()) + { + Add(State, (double)CastFieldChecked<FUInt32Property>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + else if (State.FieldType == FUInt64Property::StaticClass()) + { + Add(State, (double)CastFieldChecked<FUInt64Property>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + + // names & strings + else if (State.FieldType == FNameProperty::StaticClass()) + { + Add(State, CastFieldChecked<FNameProperty>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex).ToString()); + } + else if (State.FieldType == FStrProperty::StaticClass()) + { + Add(State, CastFieldChecked<FStrProperty>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + else if (State.FieldType == FTextProperty::StaticClass()) + { + Add(State, CastFieldChecked<FTextProperty>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex).ToString()); + } + + // classes & objects + else if (FClassProperty* ClassProperty = CastField<FClassProperty>(State.ValueProperty)) + { + Add(State, ClassProperty->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)->GetPathName()); + } + else if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(State.ValueProperty)) + { + Add(State, ObjectProperty->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + + // unsupported property type + else + { + GLog->Logf(ELogVerbosity::Warning, TEXT("FCEFJSStructSerializerBackend: Property %s cannot be serialized, because its type (%s) is not supported"), *State.ValueProperty->GetName(), *State.ValueType->GetName()); + } +} + + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructSerializerBackend.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructSerializerBackend.h new file mode 100644 index 0000000000000000000000000000000000000000..6b3476fc5df457a74784119758cc89a8a1bf57ae --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceJSStructSerializerBackend.h @@ -0,0 +1,118 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFJSStructSerializerBackend.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 + +#include "Containers/UnrealString.h" +#include "WebInterfaceBrowserSingleton.h" +#include "UObject/UnrealType.h" +#include "IStructSerializerBackend.h" +#include "CEFInterfaceJSScripting.h" + +#if PLATFORM_WINDOWS + #include "Windows/WindowsHWrapper.h" + #include "Windows/AllowWindowsPlatformTypes.h" +#endif + +#pragma push_macro("OVERRIDE") +#undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +#include "include/cef_values.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +#pragma pop_macro("OVERRIDE") + +#if PLATFORM_WINDOWS + #include "Windows/HideWindowsPlatformTypes.h" +#endif + +#endif + +class FCEFInterfaceJSScripting; +class IStructSerializerBackend; +struct FStructSerializerState; +class FWebJSScripting; +class UObject; +class FProperty; +class UStruct; + +#if WITH_CEF3 + +/** + * Implements a writer for UStruct serialization using CefDictionary. + */ +class FCEFInterfaceJSStructSerializerBackend + : public IStructSerializerBackend +{ +public: + + /** + * Creates and initializes a new instance. + * */ + FCEFInterfaceJSStructSerializerBackend(TSharedPtr<FCEFInterfaceJSScripting> InScripting) + : Scripting(InScripting), Stack(), Result() + { } + + CefRefPtr<CefDictionaryValue> GetResult() + { + return Result; + } + +public: + + // IStructSerializerBackend interface + + virtual void BeginArray(const FStructSerializerState& State) override; + virtual void BeginStructure(const FStructSerializerState& State) override; + virtual void EndArray(const FStructSerializerState& State) override; + virtual void EndStructure(const FStructSerializerState& State) override; + virtual void WriteComment(const FString& Comment) override; + virtual void WriteProperty(const FStructSerializerState& State, int32 ArrayIndex = 0) override; + +private: + + struct StackItem + { + enum {STYPE_DICTIONARY, STYPE_LIST} Kind; + FString Name; + CefRefPtr<CefDictionaryValue> DictionaryValue; + CefRefPtr<CefListValue> ListValue; + + StackItem(const FString& InName, CefRefPtr<CefDictionaryValue> InDictionary) + : Kind(STYPE_DICTIONARY) + , Name(InName) + , DictionaryValue(InDictionary) + , ListValue() + {} + + StackItem(const FString& InName, CefRefPtr<CefListValue> InList) + : Kind(STYPE_LIST) + , Name(InName) + , DictionaryValue() + , ListValue(InList) + {} + + }; + + TSharedPtr<FCEFInterfaceJSScripting> Scripting; + TArray<StackItem> Stack; + CefRefPtr<CefDictionaryValue> Result; + + void AddNull(const FStructSerializerState& State); + void Add(const FStructSerializerState& State, bool Value); + void Add(const FStructSerializerState& State, int32 Value); + void Add(const FStructSerializerState& State, double Value); + void Add(const FStructSerializerState& State, FString Value); + void Add(const FStructSerializerState& State, UObject* Value); +}; + + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceLibCefIncludes.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceLibCefIncludes.h new file mode 100644 index 0000000000000000000000000000000000000000..09e9cc369bcb1c771d48640ff2e75ef171eff122 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceLibCefIncludes.h @@ -0,0 +1,51 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFInterfaceLibCefIncludes.h +#pragma once + +#if WITH_CEF3 + +#ifndef OVERRIDE +# define OVERRIDE override +#endif //OVERRIDE + +#if PLATFORM_WINDOWS +# include "Windows/WindowsHWrapper.h" +# include "Windows/AllowWindowsPlatformTypes.h" +# include "Windows/AllowWindowsPlatformAtomics.h" +#endif + +THIRD_PARTY_INCLUDES_START + +# if PLATFORM_APPLE + PRAGMA_DISABLE_DEPRECATION_WARNINGS +# endif //PLATFORM_APPLE + +# pragma push_macro("OVERRIDE") +# undef OVERRIDE // cef headers provide their own OVERRIDE macro + +# include "include/cef_app.h" +# include "include/cef_client.h" +# include "include/cef_request.h" +# include "include/cef_task.h" +# include "include/cef_render_handler.h" +# include "include/cef_resource_handler.h" +# include "include/cef_resource_request_handler.h" +# include "include/cef_request_context_handler.h" +# include "include/cef_jsdialog_handler.h" +# include "include/cef_scheme.h" +# include "include/cef_origin_whitelist.h" +# include "include/internal/cef_ptr.h" + +# pragma pop_macro("OVERRIDE") + +# if PLATFORM_APPLE + PRAGMA_ENABLE_DEPRECATION_WARNINGS +# endif //PLATFORM_APPLE + +THIRD_PARTY_INCLUDES_END + +#if PLATFORM_WINDOWS +# include "Windows/HideWindowsPlatformAtomics.h" +# include "Windows/HideWindowsPlatformTypes.h" +#endif + +#endif //WITH_CEF3 diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceResourceContextHandler.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceResourceContextHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..bd2954ea2831ecd6b69eb82c51890107ddfaf802 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceResourceContextHandler.cpp @@ -0,0 +1,188 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFInterfaceResourceContextHandler.cpp + +#include "CEF/CEFInterfaceResourceContextHandler.h" +#include "HAL/PlatformApplicationMisc.h" + +#if WITH_CEF3 + +//#define DEBUG_ONBEFORELOAD // Debug print beforebrowse steps + +#include "CEFInterfaceBrowserClosureTask.h" +#include "WebInterfaceBrowserSingleton.h" + +#define LOCTEXT_NAMESPACE "WebInterfaceBrowserHandler" + +FCEFInterfaceResourceContextHandler::FCEFInterfaceResourceContextHandler(FWebInterfaceBrowserSingleton * InOwningSingleton) : + OwningSingleton(InOwningSingleton) +{ } + + +FString _ResourceTypeToString(const CefRequest::ResourceType& Type) +{ + const static FString ResourceType_MainFrame(TEXT("MAIN_FRAME")); + const static FString ResourceType_SubFrame(TEXT("SUB_FRAME")); + const static FString ResourceType_StyleSheet(TEXT("STYLESHEET")); + const static FString ResourceType_Script(TEXT("SCRIPT")); + const static FString ResourceType_Image(TEXT("IMAGE")); + const static FString ResourceType_FontResource(TEXT("FONT_RESOURCE")); + const static FString ResourceType_SubResource(TEXT("SUB_RESOURCE")); + const static FString ResourceType_Object(TEXT("OBJECT")); + const static FString ResourceType_Media(TEXT("MEDIA")); + const static FString ResourceType_Worker(TEXT("WORKER")); + const static FString ResourceType_SharedWorker(TEXT("SHARED_WORKER")); + const static FString ResourceType_Prefetch(TEXT("PREFETCH")); + const static FString ResourceType_Favicon(TEXT("FAVICON")); + const static FString ResourceType_XHR(TEXT("XHR")); + const static FString ResourceType_Ping(TEXT("PING")); + const static FString ResourceType_ServiceWorker(TEXT("SERVICE_WORKER")); + const static FString ResourceType_CspReport(TEXT("CSP_REPORT")); + const static FString ResourceType_PluginResource(TEXT("PLUGIN_RESOURCE")); + const static FString ResourceType_Unknown(TEXT("UNKNOWN")); + + FString TypeStr; + switch (Type) + { + case CefRequest::ResourceType::RT_MAIN_FRAME: + TypeStr = ResourceType_MainFrame; + break; + case CefRequest::ResourceType::RT_SUB_FRAME: + TypeStr = ResourceType_SubFrame; + break; + case CefRequest::ResourceType::RT_STYLESHEET: + TypeStr = ResourceType_StyleSheet; + break; + case CefRequest::ResourceType::RT_SCRIPT: + TypeStr = ResourceType_Script; + break; + case CefRequest::ResourceType::RT_IMAGE: + TypeStr = ResourceType_Image; + break; + case CefRequest::ResourceType::RT_FONT_RESOURCE: + TypeStr = ResourceType_FontResource; + break; + case CefRequest::ResourceType::RT_SUB_RESOURCE: + TypeStr = ResourceType_SubResource; + break; + case CefRequest::ResourceType::RT_OBJECT: + TypeStr = ResourceType_Object; + break; + case CefRequest::ResourceType::RT_MEDIA: + TypeStr = ResourceType_Media; + break; + case CefRequest::ResourceType::RT_WORKER: + TypeStr = ResourceType_Worker; + break; + case CefRequest::ResourceType::RT_SHARED_WORKER: + TypeStr = ResourceType_SharedWorker; + break; + case CefRequest::ResourceType::RT_PREFETCH: + TypeStr = ResourceType_Prefetch; + break; + case CefRequest::ResourceType::RT_FAVICON: + TypeStr = ResourceType_Favicon; + break; + case CefRequest::ResourceType::RT_XHR: + TypeStr = ResourceType_XHR; + break; + case CefRequest::ResourceType::RT_PING: + TypeStr = ResourceType_Ping; + break; + case CefRequest::ResourceType::RT_SERVICE_WORKER: + TypeStr = ResourceType_ServiceWorker; + break; + case CefRequest::ResourceType::RT_CSP_REPORT: + TypeStr = ResourceType_CspReport; + break; + case CefRequest::ResourceType::RT_PLUGIN_RESOURCE: + TypeStr = ResourceType_PluginResource; + break; + default: + TypeStr = ResourceType_Unknown; + break; + } + return TypeStr; +} + +CefResourceRequestHandler::ReturnValue FCEFInterfaceResourceContextHandler::OnBeforeResourceLoad(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, CefRefPtr<CefRequest> Request, CefRefPtr<CefRequestCallback> Callback) +{ +#ifdef DEBUG_ONBEFORELOAD + auto url = Request->GetURL(); + auto type = Request->GetResourceType(); + auto method = Request->GetMethod(); + if (type == CefRequest::ResourceType::RT_MAIN_FRAME || type == CefRequest::ResourceType::RT_XHR) + { + GLog->Logf(ELogVerbosity::Display, TEXT("FCEFInterfaceResourceContextHandler::OnBeforeResourceLoad :%s type:%s method:%s"), url.c_str(), *_ResourceTypeToString(type), method.c_str()); + } +#endif + + if (Request->IsReadOnly()) + { + // we can't alter this request so just allow it through + return RV_CONTINUE; + } + + // Current thread is IO thread. We need to invoke BrowserWindow->GetResourceContent on the UI (aka Game) thread: + CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(this, [=]() + { + const FString LanguageHeaderText(TEXT("Accept-Language")); + const FString LocaleCode = FWebInterfaceBrowserSingleton::GetCurrentLocaleCode(); + CefRequest::HeaderMap HeaderMap; + Request->GetHeaderMap(HeaderMap); + auto LanguageHeader = HeaderMap.find(TCHAR_TO_WCHAR(*LanguageHeaderText)); + if (LanguageHeader != HeaderMap.end()) + { + (*LanguageHeader).second = TCHAR_TO_WCHAR(*LocaleCode); + } + else + { + HeaderMap.insert(std::pair<CefString, CefString>(TCHAR_TO_WCHAR(*LanguageHeaderText), TCHAR_TO_WCHAR(*LocaleCode))); + } + + bool bAllowCredentials = false; + if (OwningSingleton != nullptr) + { + bAllowCredentials = OwningSingleton->URLRequestAllowsCredentials(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str())); + } + + FInterfaceContextRequestHeaders AdditionalHeaders; + BeforeResourceLoadDelegate.ExecuteIfBound(WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()), _ResourceTypeToString(Request->GetResourceType()), AdditionalHeaders, bAllowCredentials); + + for (auto Iter = AdditionalHeaders.CreateConstIterator(); Iter; ++Iter) + { + const FString Header = Iter.Key(); + const FString Value = Iter.Value(); + HeaderMap.insert(std::pair<CefString, CefString>(TCHAR_TO_WCHAR(*Header), TCHAR_TO_WCHAR(*Value))); + } + + Request->SetHeaderMap(HeaderMap); + + Callback->Continue(true); + })); + + // Tell CEF that we're handling this asynchronously. + return RV_CONTINUE_ASYNC; +} + + + +CefRefPtr<CefResourceRequestHandler> FCEFInterfaceResourceContextHandler::GetResourceRequestHandler( CefRefPtr<CefBrowser> browser, CefRefPtr<CefFrame> frame, + CefRefPtr<CefRequest> request, bool is_navigation, bool is_download, const CefString& request_initiator, bool& disable_default_handling) +{ +#ifdef DEBUG_ONBEFORELOAD + auto url = request->GetURL(); + auto type = request->GetResourceType(); + if (type == CefRequest::ResourceType::RT_MAIN_FRAME || type == CefRequest::ResourceType::RT_XHR || type == CefRequest::ResourceType::RT_SUB_RESOURCE) + { + GLog->Logf(ELogVerbosity::Display, TEXT("FCEFInterfaceResourceContextHandler::GetResourceRequestHandler :%s type:%s"), url.c_str(), *_ResourceTypeToString(type)); + } +#endif + return this; +} + +/*void FCEFInterfaceResourceContextHandler::AddLoadCallback(IWebInterfaceBrowserOnBeforeResourceLoadHandler* pCallback) +{ + callbacks.Add(pCallback); +}*/ +#undef LOCTEXT_NAMESPACE + +#endif // WITH_CEF diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceResourceContextHandler.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceResourceContextHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..bb57f3efe298a407cae594d8035af8aed154aa40 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceResourceContextHandler.h @@ -0,0 +1,70 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFInterfaceResourceContextHandler.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 + +#include "IWebInterfaceBrowserResourceLoader.h" + +#include "CEFInterfaceLibCefIncludes.h" + + +FString _ResourceTypeToString(const CefRequest::ResourceType& Type); + +class FWebInterfaceBrowserSingleton; + + + +/** + * Implements CEF Request handling for when a browser window is still being constructed + */ +class FCEFInterfaceResourceContextHandler : + public CefRequestContextHandler + , public CefResourceRequestHandler +{ +public: + + /** Default constructor. */ + FCEFInterfaceResourceContextHandler(FWebInterfaceBrowserSingleton *InOwningSingleton); + +public: + + // CefResourceRequestHandler Interface + virtual CefResourceRequestHandler::ReturnValue OnBeforeResourceLoad( + CefRefPtr<CefBrowser> Browser, + CefRefPtr<CefFrame> Frame, + CefRefPtr<CefRequest> Request, + CefRefPtr<CefRequestCallback> Callback) override; + + // CefRequestContextHandler Interface + virtual CefRefPtr<CefResourceRequestHandler> GetResourceRequestHandler( + CefRefPtr<CefBrowser> browser, + CefRefPtr<CefFrame> frame, + CefRefPtr<CefRequest> request, + bool is_navigation, + bool is_download, + const CefString& request_initiator, + bool& disable_default_handling) override; + + +public: + FOnBeforeInterfaceContextResourceLoadDelegate& OnBeforeLoad() + { + return BeforeResourceLoadDelegate; + } + +private: + + /** Delegate for handling resource load requests */ + FOnBeforeInterfaceContextResourceLoadDelegate BeforeResourceLoadDelegate; + + /** Singleton that owns this context handler, so we can lookup browser objects from it */ + FWebInterfaceBrowserSingleton* OwningSingleton; + + // Include the default reference counting implementation. + IMPLEMENT_REFCOUNTING(FCEFInterfaceResourceContextHandler); +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceSchemeHandler.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceSchemeHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8aaa728a6ccce2ecaf0d35a71c7fb76e8d6ff4ac --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceSchemeHandler.cpp @@ -0,0 +1,206 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFSchemeHandler.cpp + +#include "CEF/CEFInterfaceSchemeHandler.h" +#include "Misc/FileHelper.h" +#include "Misc/Paths.h" +#include "IWebInterfaceBrowserSchemeHandler.h" + +#if WITH_CEF3 + +class FHandlerHeaderSetter + : public IWebInterfaceBrowserSchemeHandler::IHeaders +{ +public: + FHandlerHeaderSetter(CefRefPtr<CefResponse>& InResponse, int64& InContentLength, CefString& InRedirectUrl) + : Response(InResponse) + , ContentLength(InContentLength) + , RedirectUrl(InRedirectUrl) + , StatusCode(INDEX_NONE) + { + } + + virtual ~FHandlerHeaderSetter() + { + if (Headers.size() > 0) + { + Response->SetHeaderMap(Headers); + } + if (StatusCode != INDEX_NONE) + { + Response->SetStatus(StatusCode); + } + if (MimeType.length() > 0) + { + Response->SetMimeType(MimeType); + } + } + + virtual void SetMimeType(const TCHAR* InMimeType) override + { + MimeType = TCHAR_TO_WCHAR(InMimeType); + } + + virtual void SetStatusCode(int32 InStatusCode) override + { + StatusCode = InStatusCode; + } + + virtual void SetContentLength(int32 InContentLength) override + { + ContentLength = InContentLength; + } + + virtual void SetRedirect(const TCHAR* InRedirectUrl) override + { + RedirectUrl = TCHAR_TO_WCHAR(InRedirectUrl); + } + + virtual void SetHeader(const TCHAR* Key, const TCHAR* Value) override + { + Headers.insert(std::make_pair(CefString(TCHAR_TO_WCHAR(Key)), CefString(TCHAR_TO_WCHAR(Value)))); + } + +private: + CefRefPtr<CefResponse>& Response; + int64& ContentLength; + CefString& RedirectUrl; + CefResponse::HeaderMap Headers; + CefString MimeType; + int32 StatusCode; +}; + +class FCefInterfaceSchemeHandler + : public CefResourceHandler +{ +public: + FCefInterfaceSchemeHandler(TUniquePtr<IWebInterfaceBrowserSchemeHandler>&& InHandlerImplementation) + : HandlerImplementation(MoveTemp(InHandlerImplementation)) + { + } + + virtual ~FCefInterfaceSchemeHandler() + { + } + + // Begin CefResourceHandler interface. + virtual bool ProcessRequest(CefRefPtr<CefRequest> Request, CefRefPtr<CefCallback> Callback) override + { + if (HandlerImplementation.IsValid()) + { + return HandlerImplementation->ProcessRequest( + WCHAR_TO_TCHAR(Request->GetMethod().ToWString().c_str()), + WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()), + FSimpleDelegate::CreateLambda([Callback](){ Callback->Continue(); }) + ); + } + return false; + } + + virtual void GetResponseHeaders(CefRefPtr<CefResponse> Response, int64& ResponseLength, CefString& RedirectUrl) override + { + if (ensure(HandlerImplementation.IsValid())) + { + FHandlerHeaderSetter Headers(Response, ResponseLength, RedirectUrl); + HandlerImplementation->GetResponseHeaders(Headers); + } + } + + virtual bool ReadResponse(void* DataOut, int BytesToRead, int& BytesRead, CefRefPtr<CefCallback> Callback) override + { + if (ensure(HandlerImplementation.IsValid())) + { + return HandlerImplementation->ReadResponse( + (uint8*)DataOut, + BytesToRead, + BytesRead, + FSimpleDelegate::CreateLambda([Callback](){ Callback->Continue(); }) + ); + } + BytesRead = 0; + return false; + } + + virtual void Cancel() override + { + if (HandlerImplementation.IsValid()) + { + HandlerImplementation->Cancel(); + } + } + // End CefResourceHandler interface. + +private: + TUniquePtr<IWebInterfaceBrowserSchemeHandler> HandlerImplementation; + + // Include CEF ref counting. + IMPLEMENT_REFCOUNTING(FCefInterfaceSchemeHandler); +}; + + +class FCefInterfaceSchemeHandlerFactory + : public CefSchemeHandlerFactory +{ +public: + + FCefInterfaceSchemeHandlerFactory(IWebInterfaceBrowserSchemeHandlerFactory* InWebBrowserSchemeHandlerFactory) + : WebBrowserSchemeHandlerFactory(InWebBrowserSchemeHandlerFactory) + { + } + + // Begin CefSchemeHandlerFactory interface. + virtual CefRefPtr<CefResourceHandler> Create(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, const CefString& Scheme, CefRefPtr<CefRequest> Request) override + { + return new FCefInterfaceSchemeHandler(WebBrowserSchemeHandlerFactory->Create( + WCHAR_TO_TCHAR(Request->GetMethod().ToWString().c_str()), + WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()))); + } + // End CefSchemeHandlerFactory interface. + + bool IsUsing(IWebInterfaceBrowserSchemeHandlerFactory* InWebBrowserSchemeHandlerFactory) + { + return WebBrowserSchemeHandlerFactory == InWebBrowserSchemeHandlerFactory; + } + +private: + IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory; + + // Include CEF ref counting. + IMPLEMENT_REFCOUNTING(FCefInterfaceSchemeHandlerFactory); +}; + +void FCefInterfaceSchemeHandlerFactories::AddSchemeHandlerFactory(FString Scheme, FString Domain, IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory) +{ + checkf(WebBrowserSchemeHandlerFactory != nullptr, TEXT("WebBrowserSchemeHandlerFactory must be provided.")); + CefRefPtr<CefSchemeHandlerFactory> Factory = new FCefInterfaceSchemeHandlerFactory(WebBrowserSchemeHandlerFactory); + CefRegisterSchemeHandlerFactory(TCHAR_TO_WCHAR(*Scheme), TCHAR_TO_WCHAR(*Domain), Factory); + SchemeHandlerFactories.Emplace(MoveTemp(Scheme), MoveTemp(Domain), MoveTemp(Factory)); +} + +void FCefInterfaceSchemeHandlerFactories::RemoveSchemeHandlerFactory(IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory) +{ + checkf(WebBrowserSchemeHandlerFactory != nullptr, TEXT("WebBrowserSchemeHandlerFactory must be provided.")); + SchemeHandlerFactories.RemoveAll([WebBrowserSchemeHandlerFactory](const FFactory& Element) + { + return ((FCefInterfaceSchemeHandlerFactory*)Element.Factory.get())->IsUsing(WebBrowserSchemeHandlerFactory); + }); +} + +void FCefInterfaceSchemeHandlerFactories::RegisterFactoriesWith(CefRefPtr<CefRequestContext>& Context) +{ + if (Context) + { + for (const FFactory& SchemeHandlerFactory : SchemeHandlerFactories) + { + Context->RegisterSchemeHandlerFactory(TCHAR_TO_WCHAR(*SchemeHandlerFactory.Scheme), TCHAR_TO_WCHAR(*SchemeHandlerFactory.Domain), SchemeHandlerFactory.Factory); + } + } +} + +FCefInterfaceSchemeHandlerFactories::FFactory::FFactory(FString InScheme, FString InDomain, CefRefPtr<CefSchemeHandlerFactory> InFactory) + : Scheme(MoveTemp(InScheme)) + , Domain(MoveTemp(InDomain)) + , Factory(MoveTemp(InFactory)) +{ +} + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceSchemeHandler.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceSchemeHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..e10644ad348e61dfd46d9e86f8c09614d55ebc29 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceSchemeHandler.h @@ -0,0 +1,56 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFSchemeHandler.h + +#pragma once + +#include "CoreMinimal.h" +#include "IWebInterfaceBrowserSchemeHandler.h" + +#if WITH_CEF3 +#include "CEFInterfaceLibCefIncludes.h" + +/** + * Implementation for managing CEF custom scheme handlers. + */ +class FCefInterfaceSchemeHandlerFactories +{ +public: + /** + * Adds a custom scheme handler factory, for a given scheme and domain. The domain is ignored if the scheme is not a browser built in scheme, + * and all requests will go through this factory. + * @param Scheme The scheme name to handle. + * @param Domain The domain name to handle on the scheme. Ignored if scheme is not a built in scheme. + * @param WebBrowserSchemeHandlerFactory The factory implementation for creating request handlers for this scheme. + */ + void AddSchemeHandlerFactory(FString Scheme, FString Domain, IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory); + + /** + * Remove a custom scheme handler factory. The factory may still be used by existing open browser windows, but will no longer be provided for new ones. + * @param WebBrowserSchemeHandlerFactory The factory implementation to remove. + */ + void RemoveSchemeHandlerFactory(IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory); + + /** + * Register all scheme handler factories with the provided request context. + * @param Context The context. + */ + void RegisterFactoriesWith(CefRefPtr<CefRequestContext>& Context); + +private: + /** + * A struct to wrap storage of a factory with it's provided scheme and domain, inc ref counting for the cef representation. + */ + struct FFactory + { + public: + FFactory(FString Scheme, FString Domain, CefRefPtr<CefSchemeHandlerFactory> Factory); + FString Scheme; + FString Domain; + CefRefPtr<CefSchemeHandlerFactory> Factory; + }; + + // Array of registered handler factories. + TArray<FFactory> SchemeHandlerFactories; +}; + + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceTextInputMethodContext.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceTextInputMethodContext.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e5febe5dc86a07e4d8f5f5120bc36339b9efb7d6 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceTextInputMethodContext.cpp @@ -0,0 +1,268 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFTextInputMethodContext.cpp + +#include "CEF/CEFInterfaceTextInputMethodContext.h" + +#if WITH_CEF3 && !PLATFORM_LINUX + +#include "CEF/CEFWebInterfaceBrowserWindow.h" +#include "CEFInterfaceImeHandler.h" +#include "Framework/Application/SlateApplication.h" + + +TSharedRef<FCEFInterfaceTextInputMethodContext> FCEFInterfaceTextInputMethodContext::Create(const TSharedRef<FCEFInterfaceImeHandler>& InOwner) +{ + return MakeShareable(new FCEFInterfaceTextInputMethodContext(InOwner)); +} + +FCEFInterfaceTextInputMethodContext::FCEFInterfaceTextInputMethodContext(const TSharedRef<FCEFInterfaceImeHandler>& InOwner) + : Owner(InOwner) + , bIsComposing(false) + , CompositionBeginIndex(0) + , CompositionLength(0) + , SelectionRangeBeginIndex(0) + , SelectionRangeLength(0) + , SelectionCaretPosition(ECaretPosition::Ending) +{ + +} + +void FCEFInterfaceTextInputMethodContext::AbortComposition() +{ + bIsComposing = false; + Owner->InternalCefBrowser->GetHost()->ImeCancelComposition(); + ResetComposition(); +} + +bool FCEFInterfaceTextInputMethodContext::UpdateCachedGeometry(const FGeometry& AllottedGeometry) +{ + bool bCachedGeometryUpdated = false; + if (CachedGeometry != AllottedGeometry) + { + CachedGeometry = AllottedGeometry; + bCachedGeometryUpdated = true; + } + + return bCachedGeometryUpdated; +} + +bool FCEFInterfaceTextInputMethodContext::CEFCompositionRangeChanged(const CefRange& SelectionRange, const CefRenderHandler::RectList& CharacterBounds) +{ + if (bIsComposing) + { + if (CharacterBounds != CefCompositionBounds) + { + CefCompositionBounds = CharacterBounds; + return true; + } + } + return false; +} + +bool FCEFInterfaceTextInputMethodContext::IsComposing() +{ + return bIsComposing; +} + +bool FCEFInterfaceTextInputMethodContext::IsReadOnly() +{ + return false; +} + +uint32 FCEFInterfaceTextInputMethodContext::GetTextLength() +{ + return CompositionString.Len(); +} + +void FCEFInterfaceTextInputMethodContext::GetSelectionRange(uint32& BeginIndex, uint32& Length, ECaretPosition& CaretPosition) +{ + BeginIndex = SelectionRangeBeginIndex; + Length = SelectionRangeLength; + CaretPosition = SelectionCaretPosition; +} + +void FCEFInterfaceTextInputMethodContext::SetSelectionRange(const uint32 BeginIndex, const uint32 Length, const ECaretPosition CaretPosition) +{ + SelectionRangeBeginIndex = BeginIndex; + SelectionRangeLength = Length; + SelectionCaretPosition = CaretPosition; + + CefString Str = TCHAR_TO_WCHAR(*CompositionString); + std::vector<CefCompositionUnderline> underlines; + Owner->InternalCefBrowser->GetHost()->ImeSetComposition( + Str, + underlines, + CefRange(UINT32_MAX, UINT32_MAX), + CefRange(SelectionRangeBeginIndex, SelectionRangeLength)); +} + +void FCEFInterfaceTextInputMethodContext::GetTextInRange(const uint32 BeginIndex, const uint32 Length, FString& OutString) +{ + OutString = CompositionString.Mid(BeginIndex, Length); +} + +void FCEFInterfaceTextInputMethodContext::SetTextInRange(const uint32 BeginIndex, const uint32 Length, const FString& InString) +{ + FString NewString; + if (BeginIndex > 0) + { + NewString = CompositionString.Mid(0, BeginIndex); + } + + NewString += InString; + + if ((int32)(BeginIndex + Length) < CompositionString.Len()) + { + NewString += CompositionString.Mid(BeginIndex + Length, CompositionString.Len() - (BeginIndex + Length)); + } + CompositionString = NewString; + + CefString Str = TCHAR_TO_WCHAR(*CompositionString); + std::vector<CefCompositionUnderline> underlines; + Owner->InternalCefBrowser->GetHost()->ImeSetComposition( + Str, + underlines, + CefRange(UINT32_MAX, UINT32_MAX), + CefRange(0, Str.length())); +} + +int32 FCEFInterfaceTextInputMethodContext::GetCharacterIndexFromPoint(const FVector2D& Point) +{ + int32 ResultIdx = INDEX_NONE; + + const FVector2D LocalPoint = CachedGeometry.AbsoluteToLocal(Point); + CefPoint CefLocalPoint = CefPoint(FMath::RoundToInt(LocalPoint.X), FMath::RoundToInt(LocalPoint.Y)); + + for (uint32 CharIdx = 0; CharIdx < CefCompositionBounds.size(); CharIdx++) + { + if (CefCompositionBounds[CharIdx].Contains(CefLocalPoint)) + { + ResultIdx = CharIdx; + break; + } + } + return ResultIdx; +} + +bool FCEFInterfaceTextInputMethodContext::GetTextBounds(const uint32 BeginIndex, const uint32 Length, FVector2D& Position, FVector2D& Size) +{ + if (CefCompositionBounds.size() < BeginIndex || + CefCompositionBounds.size() < BeginIndex + Length) + { + if (CefCompositionBounds.size() > 0) + { + // Fall back to the start of the composition + Position = CachedGeometry.LocalToAbsolute(FVector2D(CefCompositionBounds[0].x, CefCompositionBounds[0].y)); + Size = FVector2D(CefCompositionBounds[0].width, CefCompositionBounds[0].height); + return false; + } + else + { + // We don't have any updated composition bounds so we'll just default to the window bounds and say we are clipped. + GetScreenBounds(Position, Size); + return true; + } + } + + FVector2D LocalSpaceMin(FLT_MAX, FLT_MAX); + FVector2D LocalSpaceMax(-FLT_MAX, -FLT_MAX); + + for (uint32 CharIdx = BeginIndex; CharIdx < BeginIndex + Length; CharIdx++) + { + if (LocalSpaceMin.X > CefCompositionBounds[CharIdx].x) + { + LocalSpaceMin.X = CefCompositionBounds[CharIdx].x; + } + + if (LocalSpaceMax.X < CefCompositionBounds[CharIdx].x + CefCompositionBounds[CharIdx].width) + { + LocalSpaceMax.X = CefCompositionBounds[CharIdx].x + CefCompositionBounds[CharIdx].width; + } + + if (LocalSpaceMin.Y > CefCompositionBounds[CharIdx].y) + { + LocalSpaceMin.Y = CefCompositionBounds[CharIdx].y; + } + + if (LocalSpaceMax.Y < CefCompositionBounds[CharIdx].y + CefCompositionBounds[CharIdx].height) + { + LocalSpaceMax.Y = CefCompositionBounds[CharIdx].y + CefCompositionBounds[CharIdx].height; + } + } + + Position = CachedGeometry.LocalToAbsolute(LocalSpaceMin); + Size = LocalSpaceMax - LocalSpaceMin; + + return false; // false means "not clipped" +} + +void FCEFInterfaceTextInputMethodContext::GetScreenBounds(FVector2D& Position, FVector2D& Size) +{ + Position = CachedGeometry.GetAccumulatedRenderTransform().GetTranslation(); + Size = TransformVector(CachedGeometry.GetAccumulatedRenderTransform(), CachedGeometry.GetLocalSize()); +} + +TSharedPtr<FGenericWindow> FCEFInterfaceTextInputMethodContext::GetWindow() +{ + if (CachedSlateWindow.IsValid()) + { + return CachedSlateWindow.Pin()->GetNativeWindow(); + } + + const TSharedPtr<SWidget> CachedSlateWidgetPtr = Owner->InternalBrowserSlateWidget.Pin(); + if (!CachedSlateWidgetPtr.IsValid()) + { + return nullptr; + } + + TSharedPtr<SWindow> SlateWindow = FSlateApplication::Get().FindWidgetWindow(CachedSlateWidgetPtr.ToSharedRef()); + CachedSlateWindow = SlateWindow; + return SlateWindow.IsValid() ? SlateWindow->GetNativeWindow() : nullptr; +} + +void FCEFInterfaceTextInputMethodContext::BeginComposition() +{ + if (!bIsComposing) + { + bIsComposing = true; + } +} + +void FCEFInterfaceTextInputMethodContext::UpdateCompositionRange(const int32 InBeginIndex, const uint32 InLength) +{ + CompositionBeginIndex = InBeginIndex; + CompositionLength = InLength; +} + +void FCEFInterfaceTextInputMethodContext::EndComposition() +{ + if (bIsComposing) + { + bIsComposing = false; + + if (CompositionString.Len() > 0) + { + CefString Result = TCHAR_TO_WCHAR(*CompositionString); + Owner->InternalCefBrowser->GetHost()->ImeCommitText(Result, CefRange(UINT32_MAX, UINT32_MAX), 0); + } + else + { + Owner->InternalCefBrowser->GetHost()->ImeCancelComposition(); + } + ResetComposition(); + } +} + +void FCEFInterfaceTextInputMethodContext::ResetComposition() +{ + CompositionString.Empty(); + CefCompositionBounds.clear(); + CompositionBeginIndex = 0; + CompositionLength = 0; + + SelectionRangeBeginIndex = 0; + SelectionRangeLength = 0; +} + +#endif + + diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceTextInputMethodContext.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceTextInputMethodContext.h new file mode 100644 index 0000000000000000000000000000000000000000..30d914be50b9927faa73a4f50d65f58e8a69b26a --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFInterfaceTextInputMethodContext.h @@ -0,0 +1,99 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFTextInputMethodContext.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 && !PLATFORM_LINUX + +#include "Layout/Geometry.h" +#include "Widgets/SWindow.h" + +#if PLATFORM_WINDOWS +#include "Windows/WindowsHWrapper.h" +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/AllowWindowsPlatformAtomics.h" +#endif + +#pragma push_macro("OVERRIDE") +#undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +#include "include/cef_client.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +#pragma pop_macro("OVERRIDE") + +#if PLATFORM_WINDOWS +#include "Windows/HideWindowsPlatformAtomics.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif + +#include "GenericPlatform/ITextInputMethodSystem.h" +#include "Layout/Geometry.h" + +class FCEFWebInterfaceBrowserWindow; +class FCEFInterfaceImeHandler; +class SWindow; + +class FCEFInterfaceTextInputMethodContext : public ITextInputMethodContext +{ +public: + + virtual ~FCEFInterfaceTextInputMethodContext() {} + + static TSharedRef<FCEFInterfaceTextInputMethodContext> Create(const TSharedRef<FCEFInterfaceImeHandler>& InOwner); + + void AbortComposition(); + + bool UpdateCachedGeometry(const FGeometry& AllottedGeometry); + + bool CEFCompositionRangeChanged(const CefRange& SelectionRange, const CefRenderHandler::RectList& CharacterBounds); + +private: + void ResetComposition(); + +public: + + // ITextInputMethodContext Interface + virtual bool IsComposing() override; + +private: + virtual bool IsReadOnly() override; + virtual uint32 GetTextLength() override; + virtual void GetSelectionRange(uint32& BeginIndex, uint32& Length, ECaretPosition& CaretPosition) override; + virtual void SetSelectionRange(const uint32 BeginIndex, const uint32 Length, const ECaretPosition CaretPosition) override; + virtual void GetTextInRange(const uint32 BeginIndex, const uint32 Length, FString& OutString) override; + virtual void SetTextInRange(const uint32 BeginIndex, const uint32 Length, const FString& InString) override; + virtual int32 GetCharacterIndexFromPoint(const FVector2D& Point) override; + virtual bool GetTextBounds(const uint32 BeginIndex, const uint32 Length, FVector2D& Position, FVector2D& Size) override; + virtual void GetScreenBounds(FVector2D& Position, FVector2D& Size) override; + virtual TSharedPtr<FGenericWindow> GetWindow() override; + virtual void BeginComposition() override; + virtual void UpdateCompositionRange(const int32 InBeginIndex, const uint32 InLength) override; + virtual void EndComposition() override; + +private: + FCEFInterfaceTextInputMethodContext(const TSharedRef<FCEFInterfaceImeHandler>& InOwner); + TSharedRef<FCEFInterfaceImeHandler> Owner; + TWeakPtr<SWindow> CachedSlateWindow; + + FGeometry CachedGeometry; + bool bIsComposing; + int32 CompositionBeginIndex; + uint32 CompositionLength; + + uint32 SelectionRangeBeginIndex; + uint32 SelectionRangeLength; + ECaretPosition SelectionCaretPosition; + + std::vector<CefRect> CefCompositionBounds; + + FString CompositionString; +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserDialog.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserDialog.h new file mode 100644 index 0000000000000000000000000000000000000000..e6e3c50411f98da0e58397af05b28af12db9597f --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserDialog.h @@ -0,0 +1,99 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFWebBrowserDialog.h + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/AssertionMacros.h" +#include "Internationalization/Text.h" + +#if WITH_CEF3 + +#if PLATFORM_WINDOWS +# include "Windows/AllowWindowsPlatformTypes.h" +#endif +#pragma push_macro("OVERRIDE") +# undef OVERRIDE // cef headers provide their own OVERRIDE macro +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +# include "include/cef_jsdialog_handler.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +#pragma pop_macro("OVERRIDE") +#if PLATFORM_WINDOWS +# include "Windows/HideWindowsPlatformTypes.h" +#endif + +#include "IWebInterfaceBrowserDialog.h" + +class FCEFWebInterfaceBrowserDialog + : public IWebInterfaceBrowserDialog +{ +public: + virtual ~FCEFWebInterfaceBrowserDialog() + {} + + // IWebBrowserDialog interface: + + virtual EWebInterfaceBrowserDialogType GetType() override + { + return Type; + } + + virtual const FText& GetMessageText() override + { + return MessageText; + } + + virtual const FText& GetDefaultPrompt() override + { + return DefaultPrompt; + } + + virtual bool IsReload() override + { + check(Type == EWebInterfaceBrowserDialogType::Unload); + return bIsReload; + } + + virtual void Continue(bool Success = true, const FText& UserResponse = FText::GetEmpty()) override + { + check(Type == EWebInterfaceBrowserDialogType::Prompt || UserResponse.IsEmpty()); + Callback->Continue(Success, TCHAR_TO_WCHAR(*UserResponse.ToString())); + } + +private: + + + EWebInterfaceBrowserDialogType Type; + FText MessageText; + FText DefaultPrompt; + bool bIsReload; + + CefRefPtr<CefJSDialogCallback> Callback; + + // Create a dialog from OnJSDialog arguments + FCEFWebInterfaceBrowserDialog(CefJSDialogHandler::JSDialogType InDialogType, const CefString& InMessageText, const CefString& InDefaultPrompt, CefRefPtr<CefJSDialogCallback> InCallback) + : Type((EWebInterfaceBrowserDialogType)InDialogType) + , MessageText(FText::FromString(WCHAR_TO_TCHAR(InMessageText.ToWString().c_str()))) + , DefaultPrompt(FText::FromString(WCHAR_TO_TCHAR(InDefaultPrompt.ToWString().c_str()))) + , bIsReload(false) + , Callback(InCallback) + {} + + // Create a dialog from OnBeforeUnloadDialog arguments + FCEFWebInterfaceBrowserDialog(const CefString& InMessageText, bool InIsReload, CefRefPtr<CefJSDialogCallback> InCallback) + : Type(EWebInterfaceBrowserDialogType::Unload) + , MessageText(FText::FromString(WCHAR_TO_TCHAR(InMessageText.ToWString().c_str()))) + , DefaultPrompt(FText::GetEmpty()) + , bIsReload(InIsReload) + , Callback(InCallback) + {}; + + friend class FCEFWebInterfaceBrowserWindow; +}; + +typedef FCEFWebInterfaceBrowserDialog FWebInterfaceBrowserDialog; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindow.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindow.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e841a7ff28b23d5b0e211f1f0f34594a92e06714 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindow.cpp @@ -0,0 +1,2950 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFWebBrowserWindow.cpp + +#include "CEF/CEFWebInterfaceBrowserWindow.h" +#include "IWebInterfaceBrowserDialog.h" +#include "UObject/Stack.h" +#include "Framework/Application/SlateApplication.h" +#include "Textures/SlateUpdatableTexture.h" +#include "HAL/PlatformApplicationMisc.h" +#include "Misc/CommandLine.h" +#include "Misc/ConfigCacheIni.h" +#include "WebInterfaceBrowserLog.h" + +#if WITH_CEF3 + +#include "CEFInterfaceBrowserPopupFeatures.h" +#include "CEFWebInterfaceBrowserDialog.h" +#include "CEFInterfaceBrowserClosureTask.h" +#include "CEFInterfaceJSScripting.h" +#include "CEFInterfaceImeHandler.h" +#include "CEFWebInterfaceBrowserWindowRHIHelper.h" +#include "CEF3Utils.h" +#include "Async/Async.h" + +#if PLATFORM_MAC +// Needed for character code definitions +#include <Carbon/Carbon.h> +#include <AppKit/NSEvent.h> +#endif + +#if PLATFORM_WINDOWS +#include "Windows/WindowsCursor.h" +typedef FWindowsCursor FPlatformCursor; +#elif PLATFORM_MAC +#include "Mac/MacCursor.h" +#include "Mac/CocoaThread.h" +#include "Mac/MacApplication.h" +#include "Mac/MacCursor.h" +typedef FMacCursor FPlatformCursor; +#else +#endif + +#if PLATFORM_LINUX + +// From ui/events/keycodes/keyboard_codes_posix.h. +enum KeyboardCode { + VKEY_BACK = 0x08, + VKEY_TAB = 0x09, + VKEY_BACKTAB = 0x0A, + VKEY_CLEAR = 0x0C, + VKEY_RETURN = 0x0D, + VKEY_SHIFT = 0x10, + VKEY_CONTROL = 0x11, + VKEY_MENU = 0x12, + VKEY_PAUSE = 0x13, + VKEY_CAPITAL = 0x14, + VKEY_KANA = 0x15, + VKEY_HANGUL = 0x15, + VKEY_JUNJA = 0x17, + VKEY_FINAL = 0x18, + VKEY_HANJA = 0x19, + VKEY_KANJI = 0x19, + VKEY_ESCAPE = 0x1B, + VKEY_CONVERT = 0x1C, + VKEY_NONCONVERT = 0x1D, + VKEY_ACCEPT = 0x1E, + VKEY_MODECHANGE = 0x1F, + VKEY_SPACE = 0x20, + VKEY_PRIOR = 0x21, + VKEY_NEXT = 0x22, + VKEY_END = 0x23, + VKEY_HOME = 0x24, + VKEY_LEFT = 0x25, + VKEY_UP = 0x26, + VKEY_RIGHT = 0x27, + VKEY_DOWN = 0x28, + VKEY_SELECT = 0x29, + VKEY_PRINT = 0x2A, + VKEY_EXECUTE = 0x2B, + VKEY_SNAPSHOT = 0x2C, + VKEY_INSERT = 0x2D, + VKEY_DELETE = 0x2E, + VKEY_HELP = 0x2F, + VKEY_0 = 0x30, + VKEY_1 = 0x31, + VKEY_2 = 0x32, + VKEY_3 = 0x33, + VKEY_4 = 0x34, + VKEY_5 = 0x35, + VKEY_6 = 0x36, + VKEY_7 = 0x37, + VKEY_8 = 0x38, + VKEY_9 = 0x39, + VKEY_A = 0x41, + VKEY_B = 0x42, + VKEY_C = 0x43, + VKEY_D = 0x44, + VKEY_E = 0x45, + VKEY_F = 0x46, + VKEY_G = 0x47, + VKEY_H = 0x48, + VKEY_I = 0x49, + VKEY_J = 0x4A, + VKEY_K = 0x4B, + VKEY_L = 0x4C, + VKEY_M = 0x4D, + VKEY_N = 0x4E, + VKEY_O = 0x4F, + VKEY_P = 0x50, + VKEY_Q = 0x51, + VKEY_R = 0x52, + VKEY_S = 0x53, + VKEY_T = 0x54, + VKEY_U = 0x55, + VKEY_V = 0x56, + VKEY_W = 0x57, + VKEY_X = 0x58, + VKEY_Y = 0x59, + VKEY_Z = 0x5A, + VKEY_LWIN = 0x5B, + VKEY_COMMAND = VKEY_LWIN, // Provide the Mac name for convenience. + VKEY_RWIN = 0x5C, + VKEY_APPS = 0x5D, + VKEY_SLEEP = 0x5F, + VKEY_NUMPAD0 = 0x60, + VKEY_NUMPAD1 = 0x61, + VKEY_NUMPAD2 = 0x62, + VKEY_NUMPAD3 = 0x63, + VKEY_NUMPAD4 = 0x64, + VKEY_NUMPAD5 = 0x65, + VKEY_NUMPAD6 = 0x66, + VKEY_NUMPAD7 = 0x67, + VKEY_NUMPAD8 = 0x68, + VKEY_NUMPAD9 = 0x69, + VKEY_MULTIPLY = 0x6A, + VKEY_ADD = 0x6B, + VKEY_SEPARATOR = 0x6C, + VKEY_SUBTRACT = 0x6D, + VKEY_DECIMAL = 0x6E, + VKEY_DIVIDE = 0x6F, + VKEY_F1 = 0x70, + VKEY_F2 = 0x71, + VKEY_F3 = 0x72, + VKEY_F4 = 0x73, + VKEY_F5 = 0x74, + VKEY_F6 = 0x75, + VKEY_F7 = 0x76, + VKEY_F8 = 0x77, + VKEY_F9 = 0x78, + VKEY_F10 = 0x79, + VKEY_F11 = 0x7A, + VKEY_F12 = 0x7B, + VKEY_F13 = 0x7C, + VKEY_F14 = 0x7D, + VKEY_F15 = 0x7E, + VKEY_F16 = 0x7F, + VKEY_F17 = 0x80, + VKEY_F18 = 0x81, + VKEY_F19 = 0x82, + VKEY_F20 = 0x83, + VKEY_F21 = 0x84, + VKEY_F22 = 0x85, + VKEY_F23 = 0x86, + VKEY_F24 = 0x87, + VKEY_NUMLOCK = 0x90, + VKEY_SCROLL = 0x91, + VKEY_LSHIFT = 0xA0, + VKEY_RSHIFT = 0xA1, + VKEY_LCONTROL = 0xA2, + VKEY_RCONTROL = 0xA3, + VKEY_LMENU = 0xA4, + VKEY_RMENU = 0xA5, + VKEY_BROWSER_BACK = 0xA6, + VKEY_BROWSER_FORWARD = 0xA7, + VKEY_BROWSER_REFRESH = 0xA8, + VKEY_BROWSER_STOP = 0xA9, + VKEY_BROWSER_SEARCH = 0xAA, + VKEY_BROWSER_FAVORITES = 0xAB, + VKEY_BROWSER_HOME = 0xAC, + VKEY_VOLUME_MUTE = 0xAD, + VKEY_VOLUME_DOWN = 0xAE, + VKEY_VOLUME_UP = 0xAF, + VKEY_MEDIA_NEXT_TRACK = 0xB0, + VKEY_MEDIA_PREV_TRACK = 0xB1, + VKEY_MEDIA_STOP = 0xB2, + VKEY_MEDIA_PLAY_PAUSE = 0xB3, + VKEY_MEDIA_LAUNCH_MAIL = 0xB4, + VKEY_MEDIA_LAUNCH_MEDIA_SELECT = 0xB5, + VKEY_MEDIA_LAUNCH_APP1 = 0xB6, + VKEY_MEDIA_LAUNCH_APP2 = 0xB7, + VKEY_OEM_1 = 0xBA, + VKEY_OEM_PLUS = 0xBB, + VKEY_OEM_COMMA = 0xBC, + VKEY_OEM_MINUS = 0xBD, + VKEY_OEM_PERIOD = 0xBE, + VKEY_OEM_2 = 0xBF, + VKEY_OEM_3 = 0xC0, + VKEY_OEM_4 = 0xDB, + VKEY_OEM_5 = 0xDC, + VKEY_OEM_6 = 0xDD, + VKEY_OEM_7 = 0xDE, + VKEY_OEM_8 = 0xDF, + VKEY_OEM_102 = 0xE2, + VKEY_OEM_103 = 0xE3, // GTV KEYCODE_MEDIA_REWIND + VKEY_OEM_104 = 0xE4, // GTV KEYCODE_MEDIA_FAST_FORWARD + VKEY_PROCESSKEY = 0xE5, + VKEY_PACKET = 0xE7, + VKEY_DBE_SBCSCHAR = 0xF3, + VKEY_DBE_DBCSCHAR = 0xF4, + VKEY_ATTN = 0xF6, + VKEY_CRSEL = 0xF7, + VKEY_EXSEL = 0xF8, + VKEY_EREOF = 0xF9, + VKEY_PLAY = 0xFA, + VKEY_ZOOM = 0xFB, + VKEY_NONAME = 0xFC, + VKEY_PA1 = 0xFD, + VKEY_OEM_CLEAR = 0xFE, + VKEY_UNKNOWN = 0, + + // POSIX specific VKEYs. Note that as of Windows SDK 7.1, 0x97-9F, 0xD8-DA, + // and 0xE8 are unassigned. + VKEY_WLAN = 0x97, + VKEY_POWER = 0x98, + VKEY_BRIGHTNESS_DOWN = 0xD8, + VKEY_BRIGHTNESS_UP = 0xD9, + VKEY_KBD_BRIGHTNESS_DOWN = 0xDA, + VKEY_KBD_BRIGHTNESS_UP = 0xE8, + + // Windows does not have a specific key code for AltGr. We use the unused 0xE1 + // (VK_OEM_AX) code to represent AltGr, matching the behaviour of Firefox on + // Linux. + VKEY_ALTGR = 0xE1, + // Windows does not have a specific key code for Compose. We use the unused + // 0xE6 (VK_ICO_CLEAR) code to represent Compose. + VKEY_COMPOSE = 0xE6, +}; + +#endif + +#if PLATFORM_MAC +// enable buffered video so we don't DoS the OpenGL API with texture uploads causing a downstream crash on macOS +#define USE_BUFFERED_VIDEO 1 +#else +#define USE_BUFFERED_VIDEO 0 +#endif + +namespace { + // Private helper class to post a callback to GetSource. + class FWebInterfaceBrowserClosureVisitor + : public CefStringVisitor + { + public: + FWebInterfaceBrowserClosureVisitor(TFunction<void (const FString&)> InClosure) + : Closure(InClosure) + { } + + virtual void Visit(const CefString& String) override + { + Closure(FString(WCHAR_TO_TCHAR(String.ToWString().c_str()))); + } + + private: + TFunction<void (const FString&)> Closure; + IMPLEMENT_REFCOUNTING(FWebInterfaceBrowserClosureVisitor); + }; +} + + +// Private helper class to smooth out video buffering, using a ringbuffer +// (cef sometimes submits multiple frames per engine frame) +class FBrowserBufferedVideo +{ +public: + FBrowserBufferedVideo(uint32 NumFrames) + : FrameWriteIndex(0) + , FrameReadIndex(0) + , FrameCountThisEngineTick(0) + , FrameCount(0) + , FrameNumberOfLastRender(-1) + { + Frames.SetNum(NumFrames); + } + + ~FBrowserBufferedVideo() + { + } + + /** + * Submits a frame to the video buffer + * @return true if this is the first frame submitted this engine tick, or false otherwise + */ + bool SubmitFrame( + int32 InWidth, + int32 InHeight, + const void* Buffer, + FIntRect Dirty) + { + check(IsInGameThread()); + check(Buffer != nullptr); + + const uint32 NumBytesPerPixel = 4; + FFrame& Frame = Frames[FrameWriteIndex]; + + // If the write buffer catches up to the read buffer, we need to release the read buffer and increment its index + if (FrameWriteIndex == FrameReadIndex && FrameCount > 0) + { + Frame.ReleaseTextureData(); + FrameReadIndex = (FrameReadIndex + 1) % Frames.Num(); + } + + check(Frame.SlateTextureData == nullptr); + Frame.SlateTextureData = new FSlateTextureData((uint8*)Buffer, InWidth, InHeight, NumBytesPerPixel); + + FrameWriteIndex = (FrameWriteIndex + 1) % Frames.Num(); + FrameCount = FMath::Min(Frames.Num(), FrameCount + 1); + FrameCountThisEngineTick++; + + return FrameCountThisEngineTick == 1; + } + + /** + * Called once per frame to get the next frame's texturedata + * @return The texture data. Can be nullptr if no frame is available + */ + FSlateTextureData* GetNextFrameTextureData() + { + // Grab the next available frame if available. Ensure we don't grab more than one frame per engine tick + check(IsInGameThread()); + FSlateTextureData* SlateTextureData = nullptr; + if ( FrameCount > 0 ) + { + // Grab the first frame we haven't submitted yet + FFrame& Frame = Frames[FrameReadIndex]; + SlateTextureData = Frame.SlateTextureData; + + // Set this to NULL because the renderthread is taking ownership + Frame.SlateTextureData = nullptr; + FrameReadIndex = (FrameReadIndex + 1) % Frames.Num(); + FrameCount--; + } + FrameCountThisEngineTick = 0; + return SlateTextureData; + } + +private: + struct FFrame + { + FFrame() + : SlateTextureData(nullptr) + {} + + ~FFrame() + { + ReleaseTextureData(); + } + + void ReleaseTextureData() + { + if (SlateTextureData) + { + delete SlateTextureData; + } + SlateTextureData = nullptr; + } + + FSlateTextureData* SlateTextureData; + }; + + TArray<FFrame> Frames; + + // Read/write position in the ringbuffer + int32 FrameWriteIndex; + int32 FrameReadIndex; + + int32 FrameCountThisEngineTick; + int32 FrameCount; + int32 FrameNumberOfLastRender; +}; + + + +FCEFWebInterfaceBrowserWindow::FCEFWebInterfaceBrowserWindow(CefRefPtr<CefBrowser> InBrowser, CefRefPtr<FCEFInterfaceBrowserHandler> InHandler, FString InUrl, TOptional<FString> InContentsToLoad, bool bInShowErrorMessage, bool bInThumbMouseButtonNavigation, bool bInUseTransparency, bool bInUseNativeCursors, bool bInJSBindingToLoweringEnabled, bool bInUsingAcceleratedPaint) + : DocumentState(EWebInterfaceBrowserDocumentState::NoDocument) + , InternalCefBrowser(InBrowser) + , WebBrowserHandler(InHandler) + , CurrentUrl(InUrl) + , ViewportSize(FIntPoint::ZeroValue) + , ViewportDPIScaleFactor(1.0f) + , bIsClosing(false) + , bIsInitialized(false) + , ContentsToLoad(InContentsToLoad) + , bShowErrorMessage(bInShowErrorMessage) + , bThumbMouseButtonNavigation(bInThumbMouseButtonNavigation) + , bUseTransparency(bInUseTransparency) + , bUsingAcceleratedPaint(bInUsingAcceleratedPaint) + , bUseNativeCursors(bInUseNativeCursors) + , Cursor(EMouseCursor::Default) + , bIsDisabled(false) + , bIsHidden(false) + , bTickedLastFrame(true) + , bNeedsResize(false) + , bDraggingWindow(false) + , PreviousKeyDownEvent() + , PreviousKeyUpEvent() + , PreviousCharacterEvent() + , bIgnoreKeyDownEvent(false) + , bIgnoreKeyUpEvent(false) + , bIgnoreCharacterEvent(false) + , bMainHasFocus(false) + , bPopupHasFocus(false) + , bSupportsMouseWheel(true) + , bRecoverFromRenderProcessCrash(false) + , ErrorCode(0) + , bDeferNavigations(false) +#if PLATFORM_MAC + , LastPaintedSharedHandle(nullptr) +#endif + , Scripting(new FCEFInterfaceJSScripting(InBrowser, bInJSBindingToLoweringEnabled)) +#if !PLATFORM_LINUX + , Ime(new FCEFInterfaceImeHandler(InBrowser)) +#endif + , RHIRenderHelper(nullptr) +#if PLATFORM_WINDOWS || PLATFORM_MAC + , bInDirectHwndMode(false) +#endif +{ + check(InBrowser.get() != nullptr); + check(!bUsingAcceleratedPaint || CanSupportAcceleratedPaint()); // make sure if accelerated paint is selected we can support it + + UpdatableTextures[0] = nullptr; + UpdatableTextures[1] = nullptr; + + if (!CreateInitialTextures()) + { + ReleaseTextures(); + } + +#if PLATFORM_WINDOWS || PLATFORM_MAC + if (InternalCefBrowser->GetHost()->GetWindowHandle() != nullptr) + { + bInDirectHwndMode = true; + } +#endif + +#if USE_BUFFERED_VIDEO + BufferedVideo = TUniquePtr<FBrowserBufferedVideo>(new FBrowserBufferedVideo(4)); +#endif +} + +void FCEFWebInterfaceBrowserWindow::ReleaseTextures() +{ + for (int I = 0; I < 2; ++I) + { + if (UpdatableTextures[I] != nullptr) + { + FSlateUpdatableTexture* TextureToRelease = UpdatableTextures[I]; + AsyncTask(ENamedThreads::GameThread, [TextureToRelease]() + { + if (FSlateApplication::IsInitialized()) + { + if (FSlateRenderer* Renderer = FSlateApplication::Get().GetRenderer()) + { + Renderer->ReleaseUpdatableTexture(TextureToRelease); + } + } + }); + + UpdatableTextures[I] = nullptr; + } + } +} + +bool FCEFWebInterfaceBrowserWindow::CreateInitialTextures() +{ + if (FSlateApplication::IsInitialized()) + { + if (FSlateRenderer* Renderer = FSlateApplication::Get().GetRenderer()) + { + if (Renderer->HasLostDevice()) + { + return false; + } + + if (bUsingAcceleratedPaint) + { + if (FCEFWebInterfaceBrowserWindowRHIHelper::BUseRHIRenderer() && RHIRenderHelper == nullptr) + { + RHIRenderHelper = new FCEFWebInterfaceBrowserWindowRHIHelper; + } + + // the accelerated paint path attaches to the texture at render time as we don't know its details until then + UpdatableTextures[0] = nullptr; + UpdatableTextures[1] = nullptr; + return true; + } + + // Create a transparent dummy texture for our buffers which will prevent slate from applying an + // undesirable quad if it happens to ask for this buffer before we get a chance to paint to it. + TArray<uint8> RawData; + RawData.AddZeroed(4); + UpdatableTextures[0] = Renderer->CreateUpdatableTexture(1, 1); + + if (Renderer->HasLostDevice()) + { + return false; + } + + UpdatableTextures[0]->UpdateTextureThreadSafeRaw(1, 1, RawData.GetData()); + + if (Renderer->HasLostDevice()) + { + return false; + } + + UpdatableTextures[1] = Renderer->CreateUpdatableTexture(1, 1); + + if (Renderer->HasLostDevice()) + { + return false; + } + + UpdatableTextures[1]->UpdateTextureThreadSafeRaw(1, 1, RawData.GetData()); + + if (Renderer->HasLostDevice()) + { + return false; + } + + return true; + } + } + + return false; +} + +FCEFWebInterfaceBrowserWindow::~FCEFWebInterfaceBrowserWindow() +{ + WebBrowserHandler->OnCreateWindow().Unbind(); + WebBrowserHandler->OnBeforePopup().Unbind(); + WebBrowserHandler->OnBeforeResourceLoad().Unbind(); + WebBrowserHandler->OnResourceLoadComplete().Unbind(); + WebBrowserHandler->OnConsoleMessage().Unbind(); + if (IsValid()) + { + UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Closing browser during destruction, this may cause a later crash."), *CurrentUrl); + CloseBrowser(true, false); + } + + ReleaseTextures(); + + BufferedVideo.Reset(); + if (RHIRenderHelper != nullptr) + { + delete RHIRenderHelper; + } + + UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Deleting browser for Url=%s."), *CurrentUrl); +} + +void FCEFWebInterfaceBrowserWindow::LoadURL(FString NewURL) +{ + RequestNavigationInternal(NewURL, FString()); +} + +void FCEFWebInterfaceBrowserWindow::LoadString(FString Contents, FString DummyURL) +{ + RequestNavigationInternal(DummyURL, Contents); +} + +TSharedRef<SViewport> FCEFWebInterfaceBrowserWindow::CreateWidget() +{ + TSharedRef<SViewport> BrowserWidgetRef = + SNew(SViewport) + .EnableGammaCorrection(false) + .EnableBlending(bUseTransparency) + .IgnoreTextureAlpha(!bUseTransparency) + .RenderTransform(this, &FCEFWebInterfaceBrowserWindow::GetWebBrowserRenderTransform); + +#if !PLATFORM_LINUX + Ime->CacheBrowserSlateInfo(BrowserWidgetRef); +#endif + + return BrowserWidgetRef; +} + +TOptional<FSlateRenderTransform> FCEFWebInterfaceBrowserWindow::GetWebBrowserRenderTransform() const +{ + TOptional<FSlateRenderTransform> LocalRenderTransform = FSlateRenderTransform(); + if (bUsingAcceleratedPaint) + { + if (RHIRenderHelper != nullptr) + { + LocalRenderTransform = RHIRenderHelper->GetWebBrowserRenderTransform(); + } + else + { + LocalRenderTransform = FSlateRenderTransform(Concatenate(FScale2D(1, -1), FVector2D(0, ViewportSize.Y))); + } + } + return LocalRenderTransform; +} + +bool FCEFWebInterfaceBrowserWindow::BlockInputInDirectHwndMode() const +{ +#if PLATFORM_WINDOWS + return bInDirectHwndMode; +#elif PLATFORM_MAC + return bInDirectHwndMode; +#endif + + return false; +} + +void FCEFWebInterfaceBrowserWindow::SetViewportSize(FIntPoint WindowSize, FIntPoint WindowPos) +{ + // SetViewportSize is called from the browser viewport tick method, which means that since we are receiving ticks, we can mark the browser as visible. + if (! bIsDisabled) + { + SetIsHidden(false); + } + bTickedLastFrame=true; + + float WindowDPIScaleFactor = 1.0f; + if (TSharedPtr<SWindow> ParentWindowPtr = ParentWindow.Pin()) + { + WindowDPIScaleFactor = ParentWindowPtr->GetNativeWindow()->GetDPIScaleFactor(); + } + + ViewportPos = WindowPos; + + // Ignore sizes that can't be seen as it forces CEF to re-render whole image + if ((WindowSize.X > 0 && WindowSize.Y > 0 && ViewportSize != WindowSize) || WindowDPIScaleFactor != ViewportDPIScaleFactor) + { + bool bFirstSize = ViewportSize == FIntPoint::ZeroValue; + ViewportSize = WindowSize; + ViewportDPIScaleFactor = WindowDPIScaleFactor; + + if (IsValid()) + { +#if PLATFORM_WINDOWS + HWND NativeHandle = InternalCefBrowser->GetHost()->GetWindowHandle(); + if (NativeHandle) + { + HWND Parent = ::GetParent(NativeHandle); + // Position is in screen coordinates, so we'll need to get the parent window location first. + RECT ParentRect = { 0, 0, 0, 0 }; + if (Parent) + { + ::GetWindowRect(Parent, &ParentRect); + } + + FIntPoint WindowSizeScaled = (FVector2D(WindowSize) * WindowDPIScaleFactor).IntPoint(); + + ::SetWindowPos(NativeHandle, 0, WindowPos.X - ParentRect.left, WindowPos.Y - ParentRect.top, WindowSizeScaled.X, WindowSizeScaled.Y, 0); + } +#elif PLATFORM_MAC + CefWindowHandle NativeWindowHandle = InternalCefBrowser->GetHost()->GetWindowHandle(); + if (NativeWindowHandle) + { + NSView* browserView = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(NativeWindowHandle); + if (TSharedPtr<SWindow> ParentWindowPtr = ParentWindow.Pin()) + { + NSWindow* parentWindow = (NSWindow*)ParentWindowPtr->GetNativeWindow()->GetOSWindowHandle(); + + const FVector2D CocoaPosition = FMacApplication::ConvertSlatePositionToCocoa(WindowPos.X, WindowPos.Y); + NSRect parentFrame = [parentWindow frame]; + NSRect Rect = NSMakeRect(CocoaPosition.X - parentFrame.origin.x, (CocoaPosition.Y - parentFrame.origin.y) - WindowSize.Y, FMath::Max(WindowSize.X, 1), FMath::Max(WindowSize.Y, 1)); + Rect = [parentWindow frameRectForContentRect : Rect]; + + [browserView setFrame : Rect] ; + } + } +#endif + + if (bFirstSize) + { + InternalCefBrowser->GetHost()->WasResized(); + } + else + { + bNeedsResize = true; + } + } + } +} + +FSlateShaderResource* FCEFWebInterfaceBrowserWindow::GetTexture(bool bIsPopup) +{ + if (UpdatableTextures[bIsPopup?1:0] != nullptr) + { + return UpdatableTextures[bIsPopup?1:0]->GetSlateResource(); + } + return nullptr; +} + +bool FCEFWebInterfaceBrowserWindow::IsValid() const +{ + return InternalCefBrowser.get() != nullptr; +} + +bool FCEFWebInterfaceBrowserWindow::IsInitialized() const +{ + return bIsInitialized; +} + +bool FCEFWebInterfaceBrowserWindow::IsClosing() const +{ + return bIsClosing; +} + +EWebInterfaceBrowserDocumentState FCEFWebInterfaceBrowserWindow::GetDocumentLoadingState() const +{ + return DocumentState; +} + +FString FCEFWebInterfaceBrowserWindow::GetTitle() const +{ + return Title; +} + +FString FCEFWebInterfaceBrowserWindow::GetUrl() const +{ + if (InternalCefBrowser != nullptr) + { + CefRefPtr<CefFrame> MainFrame = InternalCefBrowser->GetMainFrame(); + + if (MainFrame != nullptr) + { + return CurrentUrl; + } + } + + return FString(); +} + +void FCEFWebInterfaceBrowserWindow::GetSource(TFunction<void (const FString&)> Callback) const +{ + if (IsValid()) + { + InternalCefBrowser->GetMainFrame()->GetSource(new FWebInterfaceBrowserClosureVisitor(Callback)); + } + else + { + Callback(FString()); + } +} + +void FCEFWebInterfaceBrowserWindow::PopulateCefKeyEvent(const FKeyEvent& InKeyEvent, CefKeyEvent& OutKeyEvent) +{ +#if PLATFORM_MAC + OutKeyEvent.native_key_code = InKeyEvent.GetKeyCode(); + + FKey Key = InKeyEvent.GetKey(); + if (Key == EKeys::BackSpace) + { + OutKeyEvent.unmodified_character = kBackspaceCharCode; + } + else if (Key == EKeys::Tab) + { + OutKeyEvent.unmodified_character = kTabCharCode; + } + else if (Key == EKeys::Enter) + { + OutKeyEvent.unmodified_character = kReturnCharCode; + } + else if (Key == EKeys::Pause) + { + OutKeyEvent.unmodified_character = NSPauseFunctionKey; + } + else if (Key == EKeys::Escape) + { + OutKeyEvent.unmodified_character = kEscapeCharCode; + } + else if (Key == EKeys::PageUp) + { + OutKeyEvent.unmodified_character = NSPageUpFunctionKey; + } + else if (Key == EKeys::PageDown) + { + OutKeyEvent.unmodified_character = NSPageDownFunctionKey; + } + else if (Key == EKeys::End) + { + OutKeyEvent.unmodified_character = NSEndFunctionKey; + } + else if (Key == EKeys::Home) + { + OutKeyEvent.unmodified_character = NSHomeFunctionKey; + } + else if (Key == EKeys::Left) + { + OutKeyEvent.unmodified_character = NSLeftArrowFunctionKey; + } + else if (Key == EKeys::Up) + { + OutKeyEvent.unmodified_character = NSUpArrowFunctionKey; + } + else if (Key == EKeys::Right) + { + OutKeyEvent.unmodified_character = NSRightArrowFunctionKey; + } + else if (Key == EKeys::Down) + { + OutKeyEvent.unmodified_character = NSDownArrowFunctionKey; + } + else if (Key == EKeys::Insert) + { + OutKeyEvent.unmodified_character = NSInsertFunctionKey; + } + else if (Key == EKeys::Delete) + { + OutKeyEvent.unmodified_character = kDeleteCharCode; + } + else if (Key == EKeys::F1) + { + OutKeyEvent.unmodified_character = NSF1FunctionKey; + } + else if (Key == EKeys::F2) + { + OutKeyEvent.unmodified_character = NSF2FunctionKey; + } + else if (Key == EKeys::F3) + { + OutKeyEvent.unmodified_character = NSF3FunctionKey; + } + else if (Key == EKeys::F4) + { + OutKeyEvent.unmodified_character = NSF4FunctionKey; + } + else if (Key == EKeys::F5) + { + OutKeyEvent.unmodified_character = NSF5FunctionKey; + } + else if (Key == EKeys::F6) + { + OutKeyEvent.unmodified_character = NSF6FunctionKey; + } + else if (Key == EKeys::F7) + { + OutKeyEvent.unmodified_character = NSF7FunctionKey; + } + else if (Key == EKeys::F8) + { + OutKeyEvent.unmodified_character = NSF8FunctionKey; + } + else if (Key == EKeys::F9) + { + OutKeyEvent.unmodified_character = NSF9FunctionKey; + } + else if (Key == EKeys::F10) + { + OutKeyEvent.unmodified_character = NSF10FunctionKey; + } + else if (Key == EKeys::F11) + { + OutKeyEvent.unmodified_character = NSF11FunctionKey; + } + else if (Key == EKeys::F12) + { + OutKeyEvent.unmodified_character = NSF12FunctionKey; + } + else if (Key == EKeys::CapsLock) + { + OutKeyEvent.unmodified_character = 0; + OutKeyEvent.native_key_code = kVK_CapsLock; + } + else if (Key.IsModifierKey()) + { + // Setting both unmodified_character and character to 0 tells CEF that it needs to generate a NSFlagsChanged event instead of NSKeyDown/Up + OutKeyEvent.unmodified_character = 0; + + // CEF expects modifier key codes as one of the Carbon kVK_* key codes. + if (Key == EKeys::LeftCommand) + { + OutKeyEvent.native_key_code = kVK_Command; + } + else if (Key == EKeys::LeftShift) + { + OutKeyEvent.native_key_code = kVK_Shift; + } + else if (Key == EKeys::LeftAlt) + { + OutKeyEvent.native_key_code = kVK_Option; + } + else if (Key == EKeys::LeftControl) + { + OutKeyEvent.native_key_code = kVK_Control; + } + else if (Key == EKeys::RightCommand) + { + // There isn't a separate code for the right hand command key defined, but CEF seems to use the unused value before the left command keycode + OutKeyEvent.native_key_code = kVK_Command-1; + } + else if (Key == EKeys::RightShift) + { + OutKeyEvent.native_key_code = kVK_RightShift; + } + else if (Key == EKeys::RightAlt) + { + OutKeyEvent.native_key_code = kVK_RightOption; + } + else if (Key == EKeys::RightControl) + { + OutKeyEvent.native_key_code = kVK_RightControl; + } + } + else + { + OutKeyEvent.unmodified_character = InKeyEvent.GetCharacter(); + } + OutKeyEvent.character = OutKeyEvent.unmodified_character; + +#elif PLATFORM_LINUX + OutKeyEvent.native_key_code = InKeyEvent.GetKeyCode(); + FKey Key = InKeyEvent.GetKey(); + // helper macro so we can fill in all the A-Z, 0-9 keys +#define LETTER_KEY_MACRO(val, vkey) else if(Key == EKeys::val) \ + { \ + OutKeyEvent.unmodified_character = InKeyEvent.GetCharacter(); \ + OutKeyEvent.windows_key_code = vkey; \ + } \ + + if (Key == EKeys::BackSpace) + { + OutKeyEvent.windows_key_code = VKEY_BACK; + } + else if (Key == EKeys::Tab) + { + OutKeyEvent.windows_key_code = VKEY_TAB; + } + else if (Key == EKeys::Enter) + { + OutKeyEvent.windows_key_code = VKEY_RETURN; + } + else if (Key == EKeys::Pause) + { + OutKeyEvent.windows_key_code = VKEY_PAUSE; + } + else if (Key == EKeys::Escape) + { + OutKeyEvent.windows_key_code = VKEY_ESCAPE; + } + else if (Key == EKeys::PageUp) + { + OutKeyEvent.windows_key_code = VKEY_PRIOR; + } + else if (Key == EKeys::PageDown) + { + OutKeyEvent.windows_key_code = VKEY_NEXT; + } + else if (Key == EKeys::End) + { + OutKeyEvent.windows_key_code = VKEY_END; + } + else if (Key == EKeys::Home) + { + OutKeyEvent.windows_key_code = VKEY_HOME; + } + else if (Key == EKeys::Left) + { + OutKeyEvent.windows_key_code = VKEY_LEFT; + } + else if (Key == EKeys::Up) + { + OutKeyEvent.windows_key_code = VKEY_UP; + } + else if (Key == EKeys::Right) + { + OutKeyEvent.windows_key_code = VKEY_RIGHT; + } + else if (Key == EKeys::Down) + { + OutKeyEvent.windows_key_code = VKEY_DOWN; + } + else if (Key == EKeys::Insert) + { + OutKeyEvent.windows_key_code = VKEY_INSERT; + } + else if (Key == EKeys::Delete) + { + OutKeyEvent.windows_key_code = VKEY_DELETE; + } + else if (Key == EKeys::F1) + { + OutKeyEvent.windows_key_code = VKEY_F1; + } + else if (Key == EKeys::F2) + { + OutKeyEvent.windows_key_code = VKEY_F2; + } + else if (Key == EKeys::F3) + { + OutKeyEvent.windows_key_code = VKEY_F3; + } + else if (Key == EKeys::F4) + { + OutKeyEvent.windows_key_code = VKEY_F4; + } + else if (Key == EKeys::F5) + { + OutKeyEvent.windows_key_code = VKEY_F5; + } + else if (Key == EKeys::F6) + { + OutKeyEvent.windows_key_code = VKEY_F6; + } + else if (Key == EKeys::F7) + { + OutKeyEvent.windows_key_code = VKEY_F7; + } + else if (Key == EKeys::F8) + { + OutKeyEvent.windows_key_code = VKEY_F8; + } + else if (Key == EKeys::F9) + { + OutKeyEvent.windows_key_code = VKEY_F9; + } + else if (Key == EKeys::F10) + { + OutKeyEvent.windows_key_code = VKEY_F10; + } + else if (Key == EKeys::F11) + { + OutKeyEvent.windows_key_code = VKEY_F11; + } + else if (Key == EKeys::F12) + { + OutKeyEvent.windows_key_code = VKEY_F12; + } + else if (Key == EKeys::CapsLock) + { + OutKeyEvent.windows_key_code = VKEY_CAPITAL; + } + else if (Key == EKeys::LeftCommand) + { + OutKeyEvent.windows_key_code = VKEY_MENU; + } + else if (Key == EKeys::LeftShift) + { + OutKeyEvent.windows_key_code = VKEY_SHIFT; + } + else if (Key == EKeys::LeftAlt) + { + OutKeyEvent.windows_key_code = VKEY_MENU; + } + else if (Key == EKeys::LeftControl) + { + OutKeyEvent.windows_key_code = VKEY_CONTROL; + } + else if (Key == EKeys::RightCommand) + { + OutKeyEvent.windows_key_code = VKEY_MENU; + } + else if (Key == EKeys::RightShift) + { + OutKeyEvent.windows_key_code = VKEY_SHIFT; + } + else if (Key == EKeys::RightAlt) + { + OutKeyEvent.windows_key_code = VKEY_MENU; + } + else if (Key == EKeys::RightControl) + { + OutKeyEvent.windows_key_code = VKEY_CONTROL; + } + else if(Key == EKeys::NumPadOne) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD1; + } + else if(Key == EKeys::NumPadTwo) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD2; + } + else if(Key == EKeys::NumPadThree) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD3; + } + else if(Key == EKeys::NumPadFour) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD4; + } + else if(Key == EKeys::NumPadFive) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD5; + } + else if(Key == EKeys::NumPadSix) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD6; + } + else if(Key == EKeys::NumPadSeven) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD7; + } + else if(Key == EKeys::NumPadEight) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD8; + } + else if(Key == EKeys::NumPadNine) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD9; + } + else if(Key == EKeys::NumPadZero) + { + OutKeyEvent.windows_key_code = VKEY_NUMPAD0; + } + LETTER_KEY_MACRO( A, VKEY_A) + LETTER_KEY_MACRO( B, VKEY_B) + LETTER_KEY_MACRO( C, VKEY_C) + LETTER_KEY_MACRO( D, VKEY_D) + LETTER_KEY_MACRO( E, VKEY_E) + LETTER_KEY_MACRO( F, VKEY_F) + LETTER_KEY_MACRO( G, VKEY_G) + LETTER_KEY_MACRO( H, VKEY_H) + LETTER_KEY_MACRO( I, VKEY_I) + LETTER_KEY_MACRO( J, VKEY_J) + LETTER_KEY_MACRO( K, VKEY_K) + LETTER_KEY_MACRO( L, VKEY_L) + LETTER_KEY_MACRO( M, VKEY_M) + LETTER_KEY_MACRO( N, VKEY_N) + LETTER_KEY_MACRO( O, VKEY_O) + LETTER_KEY_MACRO( P, VKEY_P) + LETTER_KEY_MACRO( Q, VKEY_Q) + LETTER_KEY_MACRO( R, VKEY_R) + LETTER_KEY_MACRO( S, VKEY_S) + LETTER_KEY_MACRO( T, VKEY_T) + LETTER_KEY_MACRO( U, VKEY_U) + LETTER_KEY_MACRO( V, VKEY_V) + LETTER_KEY_MACRO( W, VKEY_W) + LETTER_KEY_MACRO( X, VKEY_X) + LETTER_KEY_MACRO( Y, VKEY_Y) + LETTER_KEY_MACRO( Z, VKEY_Z) + LETTER_KEY_MACRO( Zero, VKEY_0) + LETTER_KEY_MACRO( One, VKEY_1) + LETTER_KEY_MACRO( Two, VKEY_2) + LETTER_KEY_MACRO( Three, VKEY_3) + LETTER_KEY_MACRO( Four, VKEY_4) + LETTER_KEY_MACRO( Five, VKEY_5) + LETTER_KEY_MACRO( Six, VKEY_6) + LETTER_KEY_MACRO( Seven, VKEY_7) + LETTER_KEY_MACRO( Eight, VKEY_8) + LETTER_KEY_MACRO( Nine, VKEY_9) + else + { + OutKeyEvent.unmodified_character = InKeyEvent.GetCharacter(); + OutKeyEvent.windows_key_code = VKEY_UNKNOWN; + } +#else + OutKeyEvent.windows_key_code = InKeyEvent.GetKeyCode(); +#endif + + OutKeyEvent.modifiers = GetCefKeyboardModifiers(InKeyEvent); + //UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Modifiers: %i %i %i") , OutKeyEvent.unmodified_character, OutKeyEvent.windows_key_code, OutKeyEvent.modifiers); + +} + +#if PLATFORM_MAC +bool _FilterSystemKeyChord(const FKeyEvent& InKeyEvent) +{ + if(InKeyEvent.IsControlDown()) + { + // Special case for Mac - make sure Cmd+~ is always passed back to the OS + if (InKeyEvent.GetKey() == EKeys::Tilde) + { + return true; + } + // Special case for Mac - make sure Cmd+H is always ignored by CEF + if (InKeyEvent.GetKey() == EKeys::H ) + { + return true; + } + } + return false; +} +#endif + +bool FCEFWebInterfaceBrowserWindow::OnKeyDown(const FKeyEvent& InKeyEvent) +{ + if (IsValid() && !BlockInputInDirectHwndMode() && !bIgnoreKeyDownEvent) + { +#if PLATFORM_MAC + if(_FilterSystemKeyChord(InKeyEvent)) + return false; +#endif + PreviousKeyDownEvent = InKeyEvent; + CefKeyEvent KeyEvent; + PopulateCefKeyEvent(InKeyEvent, KeyEvent); + KeyEvent.type = KEYEVENT_RAWKEYDOWN; + InternalCefBrowser->GetHost()->SendKeyEvent(KeyEvent); + return true; + } + return false; +} + +bool FCEFWebInterfaceBrowserWindow::OnKeyUp(const FKeyEvent& InKeyEvent) +{ + if (IsValid() && !BlockInputInDirectHwndMode() && !bIgnoreKeyUpEvent) + { +#if PLATFORM_MAC + if(_FilterSystemKeyChord(InKeyEvent)) + return false; +#endif + PreviousKeyUpEvent = InKeyEvent; + CefKeyEvent KeyEvent; + PopulateCefKeyEvent(InKeyEvent, KeyEvent); + KeyEvent.type = KEYEVENT_KEYUP; + InternalCefBrowser->GetHost()->SendKeyEvent(KeyEvent); + return true; + } + return false; +} + +bool FCEFWebInterfaceBrowserWindow::OnKeyChar(const FCharacterEvent& InCharacterEvent) +{ + if (IsValid() && !BlockInputInDirectHwndMode() && !bIgnoreCharacterEvent) + { + PreviousCharacterEvent = InCharacterEvent; + CefKeyEvent KeyEvent; +#if PLATFORM_MAC || PLATFORM_LINUX + KeyEvent.character = InCharacterEvent.GetCharacter(); + KeyEvent.windows_key_code = InCharacterEvent.GetCharacter(); +#else + KeyEvent.windows_key_code = InCharacterEvent.GetCharacter(); +#endif + KeyEvent.type = KEYEVENT_CHAR; + KeyEvent.modifiers = GetCefInputModifiers(InCharacterEvent); +#if PLATFORM_WINDOWS + if (InCharacterEvent.IsAltDown() && InCharacterEvent.IsControlDown()) + { + // For german and other keyboards with an AltGR state, windows sets alt and left control down + // See OsrWindowWin::OnKeyEvent in + //https://bitbucket.org/chromiumembedded/cef/raw/c4baba880e0b28ce82845275b328a12b2407e2f0/tests/cefclient/browser/osr_window_win.cc + // from which the concept behind this check was taken + HKL CurrentKBLayout = ::GetKeyboardLayout(0); + SHORT ScanResult = ::VkKeyScanExW(InCharacterEvent.GetCharacter(), CurrentKBLayout); + if (((ScanResult >> 8) & 0xFF) == (2 | 4)) + { + // ctrl-alt pressed from this single character event so convert to AltGR + KeyEvent.modifiers &= ~(EVENTFLAG_CONTROL_DOWN | EVENTFLAG_ALT_DOWN); + KeyEvent.modifiers |= EVENTFLAG_ALTGR_DOWN; + } + } +#endif + InternalCefBrowser->GetHost()->SendKeyEvent(KeyEvent); + return true; + } + return false; +} + +FModifierKeysState FCEFWebInterfaceBrowserWindow::SlateModifiersFromCefModifiers(const CefKeyEvent& CefEvent) +{ + return FModifierKeysState((CefEvent.modifiers & EVENTFLAG_SHIFT_DOWN) != 0, + (CefEvent.modifiers & EVENTFLAG_SHIFT_DOWN) != 0, + (CefEvent.modifiers & EVENTFLAG_CONTROL_DOWN) != 0, + (CefEvent.modifiers & EVENTFLAG_CONTROL_DOWN) != 0, + (CefEvent.modifiers & EVENTFLAG_ALT_DOWN) != 0, + (CefEvent.modifiers & EVENTFLAG_ALT_DOWN) != 0, + (CefEvent.modifiers & EVENTFLAG_COMMAND_DOWN) != 0, + (CefEvent.modifiers & EVENTFLAG_COMMAND_DOWN) != 0, + (CefEvent.modifiers & EVENTFLAG_CAPS_LOCK_ON) != 0); +} + +/* This is an ugly hack to inject unhandled key events back into Slate. + During processing of the initial keyboard event, we don't know whether it is handled by the Web browser or not. + Not until after CEF calls OnKeyEvent in our CefKeyboardHandler implementation, which is after our own keyboard event handler + has returned. + The solution is to save a copy of the event and re-inject it into Slate while ensuring that we'll ignore it and bubble it up + the widget hierarchy this time around. */ +bool FCEFWebInterfaceBrowserWindow::OnUnhandledKeyEvent(const CefKeyEvent& CefEvent) +{ + bool bWasHandled = false; + if (IsValid()) + { + CefWindowHandle NativeHandle = InternalCefBrowser->GetHost()->GetWindowHandle(); + + switch (CefEvent.type) + { + case KEYEVENT_RAWKEYDOWN: + case KEYEVENT_KEYDOWN: + if (PreviousKeyDownEvent.IsSet()) + { + bWasHandled = OnUnhandledKeyDown().IsBound() && OnUnhandledKeyDown().Execute(PreviousKeyDownEvent.GetValue()); + if (!bWasHandled) + { + // If the keydown handler is not bound or if the handler returns false, indicating the key is unhandled, we bubble it up. + bIgnoreKeyDownEvent = true; + bWasHandled = FSlateApplication::Get().ProcessKeyDownEvent(PreviousKeyDownEvent.GetValue()); + bIgnoreKeyDownEvent = false; + } + PreviousKeyDownEvent.Reset(); + } + else if (NativeHandle) + { + FKey const Key = FInputKeyManager::Get().GetKeyFromCodes(CefEvent.windows_key_code, 0); + + if (Key.IsValid()) + { + FKeyEvent KeyEvent(Key, SlateModifiersFromCefModifiers(CefEvent), FSlateApplication::Get().GetUserIndexForKeyboard(), false, 0, CefEvent.windows_key_code); + + bIgnoreKeyDownEvent = true; + bWasHandled = FSlateApplication::Get().ProcessKeyDownEvent(KeyEvent); + bIgnoreKeyDownEvent = false; + } + } + break; + case KEYEVENT_KEYUP: + if (PreviousKeyUpEvent.IsSet()) + { + bWasHandled = OnUnhandledKeyUp().IsBound() && OnUnhandledKeyUp().Execute(PreviousKeyUpEvent.GetValue()); + if (!bWasHandled) + { + // If the keyup handler is not bound or if the handler returns false, indicating the key is unhandled, we bubble it up. + bIgnoreKeyUpEvent = true; + bWasHandled = FSlateApplication::Get().ProcessKeyUpEvent(PreviousKeyUpEvent.GetValue()); + bIgnoreKeyUpEvent = false; + } + PreviousKeyUpEvent.Reset(); + } + else if (NativeHandle) + { + FKey const Key = FInputKeyManager::Get().GetKeyFromCodes(CefEvent.windows_key_code, 0); + FKeyEvent KeyEvent(Key, SlateModifiersFromCefModifiers(CefEvent), FSlateApplication::Get().GetUserIndexForKeyboard(), false, 0, CefEvent.windows_key_code); + + bIgnoreKeyUpEvent = true; + bWasHandled = FSlateApplication::Get().ProcessKeyUpEvent(KeyEvent); + bIgnoreKeyUpEvent = false; + } + + break; + case KEYEVENT_CHAR: + if (PreviousCharacterEvent.IsSet()) + { + bWasHandled = OnUnhandledKeyChar().IsBound() && OnUnhandledKeyChar().Execute(PreviousCharacterEvent.GetValue()); + if (!bWasHandled) + { + // If the keychar handler is not bound or if the handler returns false, indicating the key is unhandled, we bubble it up. + bIgnoreCharacterEvent = true; + bWasHandled = FSlateApplication::Get().ProcessKeyCharEvent(PreviousCharacterEvent.GetValue()); + bIgnoreCharacterEvent = false; + } + PreviousCharacterEvent.Reset(); + } + else if (NativeHandle) + { + FCharacterEvent CharacterEvent(CefEvent.character, SlateModifiersFromCefModifiers(CefEvent), FSlateApplication::Get().GetUserIndexForKeyboard(), false); + + bIgnoreCharacterEvent = true; + bWasHandled = FSlateApplication::Get().ProcessKeyCharEvent(CharacterEvent); + bIgnoreCharacterEvent = false; + } + break; + default: + break; + } + } + return bWasHandled; +} + +bool FCEFWebInterfaceBrowserWindow::OnJSDialog(CefJSDialogHandler::JSDialogType DialogType, const CefString& MessageText, const CefString& DefaultPromptText, CefRefPtr<CefJSDialogCallback> Callback, bool& OutSuppressMessage) +{ + bool Retval = false; + if ( OnShowDialog().IsBound() ) + { + TSharedPtr<IWebInterfaceBrowserDialog> Dialog(new FCEFWebInterfaceBrowserDialog(DialogType, MessageText, DefaultPromptText, Callback)); + EWebInterfaceBrowserDialogEventResponse EventResponse = OnShowDialog().Execute(TWeakPtr<IWebInterfaceBrowserDialog>(Dialog)); + switch (EventResponse) + { + case EWebInterfaceBrowserDialogEventResponse::Handled: + Retval = true; + break; + case EWebInterfaceBrowserDialogEventResponse::Continue: + if (DialogType == JSDIALOGTYPE_ALERT) + { + // Alert dialogs don't return a value, so treat Continue the same way as Ingore + OutSuppressMessage = true; + Retval = false; + } + else + { + Callback->Continue(true, DefaultPromptText); + Retval = true; + } + break; + case EWebInterfaceBrowserDialogEventResponse::Ignore: + OutSuppressMessage = true; + Retval = false; + break; + case EWebInterfaceBrowserDialogEventResponse::Unhandled: + default: + Retval = false; + break; + } + } + return Retval; +} + +bool FCEFWebInterfaceBrowserWindow::OnBeforeUnloadDialog(const CefString& MessageText, bool IsReload, CefRefPtr<CefJSDialogCallback> Callback) +{ + bool Retval = false; + if ( OnShowDialog().IsBound() ) + { + TSharedPtr<IWebInterfaceBrowserDialog> Dialog(new FCEFWebInterfaceBrowserDialog(MessageText, IsReload, Callback)); + EWebInterfaceBrowserDialogEventResponse EventResponse = OnShowDialog().Execute(TWeakPtr<IWebInterfaceBrowserDialog>(Dialog)); + switch (EventResponse) + { + case EWebInterfaceBrowserDialogEventResponse::Handled: + Retval = true; + break; + case EWebInterfaceBrowserDialogEventResponse::Continue: + Callback->Continue(true, CefString()); + Retval = true; + break; + case EWebInterfaceBrowserDialogEventResponse::Ignore: + Callback->Continue(false, CefString()); + Retval = true; + break; + case EWebInterfaceBrowserDialogEventResponse::Unhandled: + default: + Retval = false; + break; + } + } + return Retval; +} + +void FCEFWebInterfaceBrowserWindow::OnResetDialogState() +{ + OnDismissAllDialogs().ExecuteIfBound(); +} + +void FCEFWebInterfaceBrowserWindow::OnRenderProcessTerminated(CefRequestHandler::TerminationStatus Status) +{ + if(bRecoverFromRenderProcessCrash) + { + bRecoverFromRenderProcessCrash = false; + NotifyDocumentError((int)ERR_FAILED); // Only attempt a single recovery at a time + } + + bRecoverFromRenderProcessCrash = true; + Reload(); +} + +FReply FCEFWebInterfaceBrowserWindow::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + FReply Reply = FReply::Unhandled(); + if (IsValid() && !BlockInputInDirectHwndMode()) + { + FKey Button = MouseEvent.GetEffectingButton(); + // CEF only supports left, right, and middle mouse buttons + bool bIsCefSupportedButton = (Button == EKeys::LeftMouseButton || Button == EKeys::RightMouseButton || Button == EKeys::MiddleMouseButton); + + if(bIsCefSupportedButton) + { + CefBrowserHost::MouseButtonType Type = + (Button == EKeys::LeftMouseButton ? MBT_LEFT : ( + Button == EKeys::RightMouseButton ? MBT_RIGHT : MBT_MIDDLE)); + + CefMouseEvent Event = GetCefMouseEvent(MyGeometry, MouseEvent, bIsPopup); + + // If the click happened inside a drag region we enable window dragging which will start firing OnDragWindow events on mouse move + if (Type == MBT_LEFT && IsInDragRegion(FIntPoint(Event.x, Event.y))) + { + bDraggingWindow = true; + } + + InternalCefBrowser->GetHost()->SendMouseClickEvent(Event, Type, false,1); + Reply = FReply::Handled(); + } + } + return Reply; +} + +FReply FCEFWebInterfaceBrowserWindow::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + FReply Reply = FReply::Unhandled(); + if (IsValid() && !BlockInputInDirectHwndMode()) + { + FKey Button = MouseEvent.GetEffectingButton(); + // CEF only supports left, right, and middle mouse buttons + bool bIsCefSupportedButton = (Button == EKeys::LeftMouseButton || Button == EKeys::RightMouseButton || Button == EKeys::MiddleMouseButton); + + if(bIsCefSupportedButton) + { + CefBrowserHost::MouseButtonType Type = + (Button == EKeys::LeftMouseButton ? MBT_LEFT : ( + Button == EKeys::RightMouseButton ? MBT_RIGHT : MBT_MIDDLE)); + + if (Type == MBT_LEFT) + { + bDraggingWindow = false; + } + + CefMouseEvent Event = GetCefMouseEvent(MyGeometry, MouseEvent, bIsPopup); + InternalCefBrowser->GetHost()->SendMouseClickEvent(Event, Type, true, 1); + Reply = FReply::Handled(); + } + else if(Button == EKeys::ThumbMouseButton && bThumbMouseButtonNavigation) + { + if(CanGoBack()) + { + GoBack(); + Reply = FReply::Handled(); + } + + } + else if(Button == EKeys::ThumbMouseButton2 && bThumbMouseButtonNavigation) + { + if(CanGoForward()) + { + GoForward(); + Reply = FReply::Handled(); + } + + } + } + return Reply; +} + +FReply FCEFWebInterfaceBrowserWindow::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + FReply Reply = FReply::Unhandled(); + if (IsValid() && !BlockInputInDirectHwndMode()) + { + FKey Button = MouseEvent.GetEffectingButton(); + // CEF only supports left, right, and middle mouse buttons + bool bIsCefSupportedButton = (Button == EKeys::LeftMouseButton || Button == EKeys::RightMouseButton || Button == EKeys::MiddleMouseButton); + + if(bIsCefSupportedButton) + { + CefBrowserHost::MouseButtonType Type = + (Button == EKeys::LeftMouseButton ? MBT_LEFT : ( + Button == EKeys::RightMouseButton ? MBT_RIGHT : MBT_MIDDLE)); + + CefMouseEvent Event = GetCefMouseEvent(MyGeometry, MouseEvent, bIsPopup); + InternalCefBrowser->GetHost()->SendMouseClickEvent(Event, Type, false, 2); + Reply = FReply::Handled(); + } + } + return Reply; +} + +FReply FCEFWebInterfaceBrowserWindow::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + FReply Reply = FReply::Unhandled(); + if (IsValid() && !BlockInputInDirectHwndMode()) + { + CefMouseEvent Event = GetCefMouseEvent(MyGeometry, MouseEvent, bIsPopup); + + bool bEventConsumedByDragCallback = false; + FIntRect test; + if (bDraggingWindow && OnDragWindow().IsBound()) + { + if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + { + bEventConsumedByDragCallback = OnDragWindow().Execute(MouseEvent); + } + else + { + bDraggingWindow = false; + } + } + + if (!bEventConsumedByDragCallback) + { + InternalCefBrowser->GetHost()->SendMouseMoveEvent(Event, false); + } + + Reply = FReply::Handled(); + } + return Reply; +} + +void FCEFWebInterfaceBrowserWindow::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + // Ensure we clear any tooltips if the mouse leaves the window. + SetToolTip(CefString()); + // We have no geometry here to convert our mouse event to local space so we just make a dummy event and set the moueLeave param to true + CefMouseEvent DummyEvent; + if (IsValid() && !BlockInputInDirectHwndMode()) + { + InternalCefBrowser->GetHost()->SendMouseMoveEvent(DummyEvent, true); + } + +} + +void FCEFWebInterfaceBrowserWindow::SetSupportsMouseWheel(bool bValue) +{ + bSupportsMouseWheel = bValue; +} + +bool FCEFWebInterfaceBrowserWindow::GetSupportsMouseWheel() const +{ + return bSupportsMouseWheel; +} + +FReply FCEFWebInterfaceBrowserWindow::OnTouchGesture(const FGeometry& MyGeometry, const FPointerEvent& GestureEvent, bool bIsPopup) +{ + FReply Reply = FReply::Unhandled(); + if(IsValid() && bSupportsMouseWheel && !BlockInputInDirectHwndMode()) + { + const EGestureEvent GestureType = GestureEvent.GetGestureType(); + const FVector2D& GestureDelta = GestureEvent.GetGestureDelta(); + if ( GestureType == EGestureEvent::Scroll ) + { + CefMouseEvent Event = GetCefMouseEvent(MyGeometry, GestureEvent, bIsPopup); + InternalCefBrowser->GetHost()->SendMouseWheelEvent(Event, GestureDelta.X, GestureDelta.Y); + Reply = FReply::Handled(); + } + } + + return Reply; +} + +FReply FCEFWebInterfaceBrowserWindow::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + FReply Reply = FReply::Unhandled(); + if(IsValid() && bSupportsMouseWheel && !BlockInputInDirectHwndMode()) + { +#if PLATFORM_WINDOWS + // The original delta is reduced so this should bring it back to what CEF expects + // see WindowsApplication.cpp , case WM_MOUSEWHEEL: + const float SpinFactor = 120.0f; +#else + // other OS's seem to want us to scale by "line height" here, so pick a magic number + // 50 matches a single mouse wheel tick in movement as compared to Chrome + const float SpinFactor = 50.0f; +#endif + const float TrueDelta = MouseEvent.GetWheelDelta() * SpinFactor; + if (fabs(TrueDelta) > 0.001f) + { + CefMouseEvent Event = GetCefMouseEvent(MyGeometry, MouseEvent, bIsPopup); + InternalCefBrowser->GetHost()->SendMouseWheelEvent(Event, + MouseEvent.IsShiftDown() ? TrueDelta : 0, + !MouseEvent.IsShiftDown() ? TrueDelta : 0); + } + Reply = FReply::Handled(); + } + return Reply; +} + +void FCEFWebInterfaceBrowserWindow::OnFocus(bool SetFocus, bool bIsPopup) +{ + if (bIsPopup) + { + bPopupHasFocus = SetFocus; + } + else + { + bMainHasFocus = SetFocus; + } + +#if !PLATFORM_LINUX + Ime->SetFocus(!bPopupHasFocus && bMainHasFocus); +#endif + + // Only notify focus if there is no popup menu with focus, as SendFocusEvent will dismiss any popup menus. + if (IsValid() && !bPopupHasFocus) + { + InternalCefBrowser->GetHost()->SendFocusEvent(bMainHasFocus); + } +} + +void FCEFWebInterfaceBrowserWindow::OnCaptureLost() +{ + if (IsValid()) + { + InternalCefBrowser->GetHost()->SendCaptureLostEvent(); + } +} + +bool FCEFWebInterfaceBrowserWindow::CanGoBack() const +{ + if (IsValid()) + { + return InternalCefBrowser->CanGoBack(); + } + return false; +} + +void FCEFWebInterfaceBrowserWindow::GoBack() +{ + if (IsValid()) + { + InternalCefBrowser->GoBack(); + } +} + +bool FCEFWebInterfaceBrowserWindow::CanGoForward() const +{ + if (IsValid()) + { + return InternalCefBrowser->CanGoForward(); + } + return false; +} + +void FCEFWebInterfaceBrowserWindow::GoForward() +{ + if (IsValid()) + { + InternalCefBrowser->GoForward(); + } +} + +bool FCEFWebInterfaceBrowserWindow::IsLoading() const +{ + if (IsValid()) + { + return InternalCefBrowser->IsLoading(); + } + return false; +} + +void FCEFWebInterfaceBrowserWindow::Reload() +{ + if (IsValid()) + { + InternalCefBrowser->Reload(); + } +} + +void FCEFWebInterfaceBrowserWindow::StopLoad() +{ + if (IsValid()) + { + InternalCefBrowser->StopLoad(); + } +} + +void FCEFWebInterfaceBrowserWindow::ExecuteJavascript(const FString& Script) +{ + if (IsValid()) + { + CefRefPtr<CefFrame> frame = InternalCefBrowser->GetMainFrame(); + frame->ExecuteJavaScript(TCHAR_TO_UTF8(*Script), frame->GetURL(), 0); + } +} + + +void FCEFWebInterfaceBrowserWindow::CloseBrowser(bool bForce, bool bBlockTillClosed) +{ + if (IsValid()) + { + CefRefPtr<CefBrowserHost> Host = InternalCefBrowser->GetHost(); +#if PLATFORM_MAC + CefWindowHandle NativeWindowHandle = Host->GetWindowHandle(); + if (NativeWindowHandle != nullptr) + { + NSView *browserView = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(NativeWindowHandle); + if (browserView != nil) + [browserView removeFromSuperview]; + } +#endif + // In case this is called from inside a CEF event handler, use CEF's task mechanism to + // postpone the actual closing of the window until it is safe. + CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(nullptr, [=]() + { + // if blocking till closed for the close here + Host->CloseBrowser(bForce||bBlockTillClosed); + })); + + if (bBlockTillClosed) + { + SetIsHidden(true); // hide the window as we close it + float CloseWaitTimeout = 1.0f; + // BUGBUG Alfred - I think 1 second should be enough wait, remove these config settings + // if we don't end up having to tune timeouts in the field. + GConfig->GetFloat(TEXT("Browser"), TEXT("CloseWaitTimeout"), CloseWaitTimeout, GEngineIni); + if (IsEngineExitRequested()) + { + // wait longer if the app is shutting down + GConfig->GetFloat(TEXT("Browser"), TEXT("CloseWaitTimeoutAppExit"), CloseWaitTimeout, GEngineIni); + } + const double StartWaitAppTime = FPlatformTime::Seconds(); + while (InternalCefBrowser != nullptr) + { + if (FPlatformTime::Seconds() - StartWaitAppTime > CloseWaitTimeout ) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("CloseBrowser - took more than %0.2f second to close. Abandoning wait..."), CloseWaitTimeout); + break; // don't spin forever + } + FPlatformProcess::Sleep(0.01); + // CEF needs the windows message pump run to be able to finish closing a browser, so run it manually here + FSlateApplication::Get().PumpMessages(); + CefDoMessageLoopWork(); + } + } + } +} + +CefRefPtr<CefBrowser> FCEFWebInterfaceBrowserWindow::GetCefBrowser() +{ + return InternalCefBrowser; +} + +void FCEFWebInterfaceBrowserWindow::SetTitle(const CefString& InTitle) +{ + Title = WCHAR_TO_TCHAR(InTitle.ToWString().c_str()); + TitleChangedEvent.Broadcast(Title); +} + +void FCEFWebInterfaceBrowserWindow::SetUrl(const CefString& Url) +{ + CurrentUrl = WCHAR_TO_TCHAR(Url.ToWString().c_str()); + OnUrlChanged().Broadcast(CurrentUrl); +} + +void FCEFWebInterfaceBrowserWindow::SetToolTip(const CefString& CefToolTip) +{ + FString NewToolTipText = WCHAR_TO_TCHAR(CefToolTip.ToWString().c_str()); + if (ToolTipText != NewToolTipText) + { + ToolTipText = NewToolTipText; + OnToolTip().Broadcast(ToolTipText); + } +} + +void FCEFWebInterfaceBrowserWindow::GetViewRect(CefRect& Rect) +{ + if (ViewportSize == FIntPoint::ZeroValue) + { + Rect.width = 1; // CEF requires a minimum of a 1x1 window to correctly run + Rect.height = 1; + } + else + { + // CEF requires a minimum of a 1px in each dimension to correctly run + Rect.width = FMath::Max( ViewportSize.X, 1); + Rect.height = FMath::Max( ViewportSize.Y, 1); + } +} + +int FCEFWebInterfaceBrowserWindow::GetLoadError() +{ + return ErrorCode; +} + +void FCEFWebInterfaceBrowserWindow::NotifyDocumentError( + CefLoadHandler::ErrorCode InErrorCode, + const CefString& ErrorText, + const CefString& FailedUrl) +{ + FString Url = WCHAR_TO_TCHAR(FailedUrl.ToWString().c_str()); + + if (InErrorCode == ERR_ABORTED) + { + // Aborting navigation is not an error case but we do need to wait for any existing navigations, handled via OnBeforeBrowse(), to fully abort before we can initiate a new navigation. + if (!PendingAbortUrl.IsEmpty() && PendingAbortUrl == Url) + { + PendingAbortUrl.Empty(); + bDeferNavigations = false; + + if (HasPendingNavigation()) + { + ProcessPendingNavigation(); + } + } + return; + } + + if (IsShowingErrorMessages()) + { + // Display a load error message. Note: The user's code will still have a chance to handle this error after this error message is displayed. + FFormatNamedArguments Args; + { + Args.Add(TEXT("FailedUrl"), FText::FromString(Url)); + Args.Add(TEXT("ErrorText"), FText::FromString(WCHAR_TO_TCHAR(ErrorText.ToWString().c_str()))); + Args.Add(TEXT("ErrorCode"), FText::AsNumber((int)InErrorCode)); + } + FText ErrorMsg = FText::Format(NSLOCTEXT("WebBrowserHandler", "WebBrowserLoadError", "Failed to load URL {FailedUrl} with error {ErrorText} ({ErrorCode})."), Args); + FString ErrorHTML = TEXT("<html><body bgcolor=\"white\"><h2>") + ErrorMsg.ToString() + TEXT("</h2></body></html>"); + + LoadString(ErrorHTML, Url); + } + + NotifyDocumentError((int)InErrorCode); +} + +void FCEFWebInterfaceBrowserWindow::NotifyDocumentError(int InErrorCode) +{ + ErrorCode = InErrorCode; + DocumentState = EWebInterfaceBrowserDocumentState::Error; + DocumentStateChangedEvent.Broadcast(DocumentState); +} + +void FCEFWebInterfaceBrowserWindow::NotifyDocumentLoadingStateChange(bool IsLoading) +{ + if (! IsLoading) + { + bIsInitialized = true; + + if (bRecoverFromRenderProcessCrash) + { + bRecoverFromRenderProcessCrash = false; + // Toggle hidden/visible state to get OnPaint calls from CEF. + SetIsHidden(true); + SetIsHidden(false); + } + + // Compatibility with Android script bindings: dispatch a custom ue:ready event when the document is fully loaded + ExecuteJavascript(TEXT("document.dispatchEvent(new CustomEvent('ue:ready', {details: window.ue}));")); + } + + // Ignore a load completed notification if there was an error. + // For load started, reset any errors from previous page load. + if (IsLoading || DocumentState != EWebInterfaceBrowserDocumentState::Error) + { + ErrorCode = 0; + DocumentState = IsLoading + ? EWebInterfaceBrowserDocumentState::Loading + : EWebInterfaceBrowserDocumentState::Completed; + DocumentStateChangedEvent.Broadcast(DocumentState); + } +} + +FSlateRenderer* const FCEFWebInterfaceBrowserWindow::GetRenderer() +{ + if (FSlateApplication::IsInitialized()) + { + if (FSlateRenderer* Renderer = FSlateApplication::Get().GetRenderer()) + { + if (!Renderer->HasLostDevice()) + { + return Renderer; + } + } + } + + ReleaseTextures(); + return nullptr; +} + +void FCEFWebInterfaceBrowserWindow::HandleRenderingError() +{ + // GetRenderer handles errors already + GetRenderer(); +} + +void FCEFWebInterfaceBrowserWindow::OnPaint(CefRenderHandler::PaintElementType Type, const CefRenderHandler::RectList& DirtyRects, const void* Buffer, int Width, int Height) +{ + bool bNeedsRedraw = false; + if (bUsingAcceleratedPaint) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Accelerated CEF rendering selected but OnPaint called. Disabling accelerated rendering for this browser window.")); + bUsingAcceleratedPaint = false; + if (UpdatableTextures[Type] != nullptr) + { + if (FSlateRenderer* const Renderer = GetRenderer()) + { + Renderer->ReleaseUpdatableTexture(UpdatableTextures[Type]); + HandleRenderingError(); + UpdatableTextures[Type] = nullptr; + } + } + } + + if (UpdatableTextures[Type] == nullptr) + { + if (FSlateRenderer* const Renderer = GetRenderer()) + { + UpdatableTextures[Type] = Renderer->CreateUpdatableTexture(Width, Height); + HandleRenderingError(); + } + } + + if (UpdatableTextures[Type] != nullptr) + { + // Note that with more recent versions of CEF, the DirtyRects will always contain a single element, as it merges all dirty areas into a single rectangle before calling OnPaint + // In case that should change in the future, we'll simply update the entire area if DirtyRects is not a single element. + FIntRect Dirty = (DirtyRects.size() == 1) ? FIntRect(DirtyRects[0].x, DirtyRects[0].y, DirtyRects[0].x + DirtyRects[0].width, DirtyRects[0].y + DirtyRects[0].height) : FIntRect(); + + if (Type == PET_VIEW && BufferedVideo.IsValid() ) + { + // If we're using bufferedVideo, submit the frame to it + bNeedsRedraw = BufferedVideo->SubmitFrame(Width, Height, Buffer, Dirty); + } + else + { + UpdatableTextures[Type]->UpdateTextureThreadSafeRaw(Width, Height, Buffer, Dirty); + HandleRenderingError(); + + if (Type == PET_POPUP && bShowPopupRequested) + { + bShowPopupRequested = false; + bPopupHasFocus = true; + + const float DPIScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(PopupPosition.X, PopupPosition.Y); + FIntPoint PopupSize = FIntPoint(Width / DPIScale, Height / DPIScale); + + FIntRect PopupRect = FIntRect(PopupPosition, PopupPosition + PopupSize); + OnShowPopup().Broadcast(PopupRect); + } + bNeedsRedraw = true; + } + } + + bIsInitialized = true; + if (bNeedsRedraw) + { + NeedsRedrawEvent.Broadcast(); + } +} + +void FCEFWebInterfaceBrowserWindow::OnAcceleratedPaint(CefRenderHandler::PaintElementType Type, const CefRenderHandler::RectList& DirtyRects, void* SharedHandle) +{ + bool bNeedsRedraw = false; + if (!bUsingAcceleratedPaint) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Accelerated CEF rendering NOT selected but OnAcceleratedPaint called. Enabling accelerated rendering for this browser window.")); + bUsingAcceleratedPaint = true; + if (UpdatableTextures[Type] != nullptr) + { + if (FSlateRenderer* const Renderer = GetRenderer()) + { + Renderer->ReleaseUpdatableTexture(UpdatableTextures[Type]); + HandleRenderingError(); + UpdatableTextures[Type] = nullptr; + } + } + } + +#if PLATFORM_MAC + // an IOSurface backs the handle here and its texture is automatically updated if changed, so we only need to + // update our texture if the backing handle itself changed + if (LastPaintedSharedHandle == SharedHandle) + return; + LastPaintedSharedHandle = SharedHandle; +#endif + FIntRect Dirty = (DirtyRects.size() == 1) ? FIntRect(DirtyRects[0].x, DirtyRects[0].y, DirtyRects[0].x + DirtyRects[0].width, DirtyRects[0].y + DirtyRects[0].height) : FIntRect(); + if (UpdatableTextures[Type] == nullptr) + { + if (FSlateRenderer* const Renderer = GetRenderer()) + { + if (RHIRenderHelper) + { + UpdatableTextures[Type] = RHIRenderHelper->CreateTexture(SharedHandle); + } + else + { + UpdatableTextures[Type] = Renderer->CreateSharedHandleTexture(SharedHandle); + } + Dirty = FIntRect(); // force a fully copy when we make a new texture + HandleRenderingError(); + } + } + if (UpdatableTextures[Type] != nullptr) + { +#if PLATFORM_WINDOWS + if (RHIRenderHelper) + { + RHIRenderHelper->UpdateSharedHandleTexture(SharedHandle, UpdatableTextures[Type], Dirty.Scale(ViewportDPIScaleFactor)); + } + else + { + UpdatableTextures[Type]->UpdateTextureThreadSafeWithKeyedTextureHandle(SharedHandle, 1, 0, Dirty.Scale(ViewportDPIScaleFactor)); + } +#else + UpdatableTextures[Type]->UpdateTextureThreadSafeWithKeyedTextureHandle(SharedHandle, 1, 0, Dirty.Scale(ViewportDPIScaleFactor)); +#endif + + bNeedsRedraw = true; + if (Type == PET_POPUP && bShowPopupRequested) + { + bShowPopupRequested = false; + bPopupHasFocus = true; + + const float DPIScale = FPlatformApplicationMisc::GetDPIScaleFactorAtPoint(PopupPosition.X, PopupPosition.Y); + FIntPoint PopupSize = FIntPoint(UpdatableTextures[Type]->GetSlateResource()->GetWidth() / DPIScale, UpdatableTextures[Type]->GetSlateResource()->GetHeight() / DPIScale); + + FIntRect PopupRect = FIntRect(PopupPosition, PopupPosition + PopupSize); + OnShowPopup().Broadcast(PopupRect); + } + + } + + bIsInitialized = true; + if (bNeedsRedraw) + { + NeedsRedrawEvent.Broadcast(); + } +} + + + +void FCEFWebInterfaceBrowserWindow::UpdateVideoBuffering() +{ + if (BufferedVideo.IsValid() && UpdatableTextures[PET_VIEW] != nullptr ) + { + FSlateTextureData* SlateTextureData = BufferedVideo->GetNextFrameTextureData(); + if (SlateTextureData != nullptr ) + { + UpdatableTextures[PET_VIEW]->UpdateTextureThreadSafeWithTextureData(SlateTextureData); + HandleRenderingError(); + } + } +} + +#if PLATFORM_WINDOWS +bool FCEFWebInterfaceBrowserWindow::LoadCustomCEF3Cursor(cef_cursor_type_t Type) +{ + // generated from the ui_unscaled_resources.h file in a CEF build +#define IDC_PAN_EAST 25846 +#define IDC_PAN_MIDDLE 25847 +#define IDC_PAN_MIDDLE_HORIZONTAL 25848 +#define IDC_PAN_MIDDLE_VERTICAL 25849 +#define IDC_PAN_NORTH 25850 +#define IDC_PAN_NORTH_EAST 25851 +#define IDC_PAN_NORTH_WEST 25852 +#define IDC_PAN_SOUTH 25853 +#define IDC_PAN_SOUTH_EAST 25854 +#define IDC_PAN_SOUTH_WEST 25855 +#define IDC_PAN_WEST 25856 + + HINSTANCE CEF3ModuleHandle = (HINSTANCE)CEF3Utils::GetCEF3ModuleHandle(); + if (CEF3ModuleHandle != nullptr) + { + HCURSOR customCursor = 0; + switch (Type) { + case CT_MIDDLE_PANNING_HORIZONTAL: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_MIDDLE_HORIZONTAL)); + break; + case CT_MIDDLE_PANNING_VERTICAL: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_MIDDLE_VERTICAL)); + break; + case CT_MIDDLEPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_MIDDLE)); + break; + case CT_SOUTHPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_SOUTH)); + break; + case CT_NORTHPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_NORTH)); + break; + case CT_EASTPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_EAST)); + break; + case CT_WESTPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_WEST)); + break; + case CT_NORTHEASTPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_NORTH_EAST)); + break; + case CT_NORTHWESTPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_NORTH_WEST)); + break; + case CT_SOUTHEASTPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_SOUTH_EAST)); + break; + case CT_SOUTHWESTPANNING: + customCursor = LoadCursor(CEF3ModuleHandle, MAKEINTRESOURCE(IDC_PAN_SOUTH_WEST)); + break; + } + + if (customCursor) + { + TSharedPtr<ICursor> PlatformCursor = FSlateApplication::Get().GetPlatformCursor(); + + if (PlatformCursor.IsValid()) + { + PlatformCursor->SetTypeShape(EMouseCursor::Custom, (void*)customCursor); + Cursor = EMouseCursor::Custom; + ::SetCursor(customCursor); + } + return true; + } + } + return false; +} +#endif + +bool FCEFWebInterfaceBrowserWindow::OnCursorChange(CefCursorHandle CefCursor, cef_cursor_type_t Type, const CefCursorInfo& CustomCursorInfo) +{ +#if PLATFORM_WINDOWS || PLATFORM_MAC + if (bUseNativeCursors) + { + switch (Type) { + // Map the basic 3 cursor types directly to Slate types on all platforms + case CT_NONE: + Cursor = EMouseCursor::None; + break; + case CT_POINTER: + Cursor = EMouseCursor::Default; + break; + case CT_IBEAM: + Cursor = EMouseCursor::TextEditBeam; + break; + default: + // Platform specific support for native cursor types + { + #if PLATFORM_WINDOWS + // check if we have a cursor in libcef.dll we can use + if (LoadCustomCEF3Cursor(Type)) + return true; + #endif + TSharedPtr<ICursor> PlatformCursor = FSlateApplication::Get().GetPlatformCursor(); + + if (PlatformCursor.IsValid()) + { + PlatformCursor->SetTypeShape(EMouseCursor::Custom, (void*)CefCursor); + Cursor = EMouseCursor::Custom; + } + } + break; + } + } + else +#endif + switch (Type) { + case CT_NONE: + Cursor = EMouseCursor::None; + break; + case CT_POINTER: + Cursor = EMouseCursor::Default; + break; + case CT_IBEAM: + Cursor = EMouseCursor::TextEditBeam; + break; + case CT_VERTICALTEXT: + Cursor = EMouseCursor::TextEditBeam; + break; + case CT_EASTRESIZE: + case CT_WESTRESIZE: + case CT_EASTWESTRESIZE: + case CT_COLUMNRESIZE: + Cursor = EMouseCursor::ResizeLeftRight; + break; + case CT_NORTHRESIZE: + case CT_SOUTHRESIZE: + case CT_NORTHSOUTHRESIZE: + case CT_ROWRESIZE: + Cursor = EMouseCursor::ResizeUpDown; + break; + case CT_NORTHWESTRESIZE: + case CT_SOUTHEASTRESIZE: + case CT_NORTHWESTSOUTHEASTRESIZE: + Cursor = EMouseCursor::ResizeSouthEast; + break; + case CT_NORTHEASTRESIZE: + case CT_SOUTHWESTRESIZE: + case CT_NORTHEASTSOUTHWESTRESIZE: + Cursor = EMouseCursor::ResizeSouthWest; + break; + case CT_MOVE: + case CT_MIDDLEPANNING: + case CT_EASTPANNING: + case CT_NORTHPANNING: + case CT_NORTHEASTPANNING: + case CT_NORTHWESTPANNING: + case CT_SOUTHPANNING: + case CT_SOUTHEASTPANNING: + case CT_SOUTHWESTPANNING: + case CT_WESTPANNING: + Cursor = EMouseCursor::CardinalCross; + break; + case CT_CROSS: + Cursor = EMouseCursor::Crosshairs; + break; + case CT_HAND: + Cursor = EMouseCursor::Hand; + break; + case CT_GRAB: + Cursor = EMouseCursor::GrabHand; + break; + case CT_GRABBING: + Cursor = EMouseCursor::GrabHandClosed; + break; + case CT_NOTALLOWED: + case CT_NODROP: + Cursor = EMouseCursor::SlashedCircle; + break; + default: + Cursor = EMouseCursor::Default; + break; + } + // Tell Slate to update the cursor now + FSlateApplication::Get().QueryCursor(); + return false; +} + +EWebInterfaceTransitionSource _TransitionTypeToSourceEnum(const CefRequest::TransitionType& Type) +{ + CefRequest::TransitionType TransitionSource = (CefRequest::TransitionType)(Type & TT_SOURCE_MASK); + switch (TransitionSource) + { + case TT_LINK: return EWebInterfaceTransitionSource::Link; + case TT_EXPLICIT: return EWebInterfaceTransitionSource::Explicit; + case TT_AUTO_SUBFRAME: return EWebInterfaceTransitionSource::AutoSubframe; + case TT_FORM_SUBMIT: return EWebInterfaceTransitionSource::FormSubmit; + case TT_RELOAD: return EWebInterfaceTransitionSource::Reload; + default: return EWebInterfaceTransitionSource::Unknown; + } + return EWebInterfaceTransitionSource::Unknown; +} + +EWebInterfaceTransitionSourceQualifier _TransitionTypeToSourceQualifierEnum(const CefRequest::TransitionType& Type) +{ + CefRequest::TransitionType TransitionSourceQualifier = (CefRequest::TransitionType)(Type & TT_QUALIFIER_MASK); + switch (TransitionSourceQualifier) + { + case TT_BLOCKED_FLAG: return EWebInterfaceTransitionSourceQualifier::Blocked; + case TT_FORWARD_BACK_FLAG: return EWebInterfaceTransitionSourceQualifier::ForwardBack; + case TT_CHAIN_START_FLAG: return EWebInterfaceTransitionSourceQualifier::ChainStart; + case TT_CHAIN_END_FLAG: return EWebInterfaceTransitionSourceQualifier::ChainEnd; + case TT_CLIENT_REDIRECT_FLAG: return EWebInterfaceTransitionSourceQualifier::ClientRedirect; + case TT_SERVER_REDIRECT_FLAG: return EWebInterfaceTransitionSourceQualifier::ServerRedirect; + default: return EWebInterfaceTransitionSourceQualifier::Unknown; + } + return EWebInterfaceTransitionSourceQualifier::Unknown; +} + +bool FCEFWebInterfaceBrowserWindow::OnBeforeBrowse( CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, CefRefPtr<CefRequest> Request, bool user_gesture, bool bIsRedirect ) +{ + if (InternalCefBrowser != nullptr && InternalCefBrowser->IsSame(Browser)) + { + CefRefPtr<CefFrame> MainFrame = InternalCefBrowser->GetMainFrame(); + if (MainFrame.get() != nullptr) + { + if(OnBeforeBrowse().IsBound()) + { + FString Url = WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()); + bool bIsMainFrame = Frame->IsMain(); + + FWebNavigationRequest RequestDetails; + RequestDetails.bIsRedirect = bIsRedirect; + RequestDetails.bIsMainFrame = bIsMainFrame; + RequestDetails.bIsExplicitTransition = Request->GetTransitionType() == TT_EXPLICIT; + + const CefRequest::TransitionType RequestTransitionType = Request->GetTransitionType(); + RequestDetails.TransitionSource = _TransitionTypeToSourceEnum(RequestTransitionType); + RequestDetails.TransitionSourceQualifier = _TransitionTypeToSourceQualifierEnum(RequestTransitionType); + RequestDetails.bIsExplicitTransition = RequestDetails.TransitionSource == EWebInterfaceTransitionSource::Explicit; + + if (bIsMainFrame) + { + // We need to defer all future navigations until we can determine if this current navigation is going to be handled or not + bDeferNavigations = true; + } + + bool bHandled = OnBeforeBrowse().Execute(Url, RequestDetails); + if (bIsMainFrame) + { + // If the browse request is handled and this is the main frame we must defer LoadUrl() calls until the request is fully aborted in/after NotifyDocumentError + bDeferNavigations = bHandled && !bIsRedirect; + if (bDeferNavigations) + { + PendingAbortUrl = Url; + } + else if (HasPendingNavigation()) + { + ProcessPendingNavigation(); + } + } + return bHandled; + } + } + } + return false; +} + + + +FString _URLRequestStatusToString(const CefResourceRequestHandler::URLRequestStatus& Status) +{ + const static FString URLRequestStatus_Success(TEXT("SUCCESS")); + const static FString URLRequestStatus_IoPending(TEXT("IO_PENDING")); + const static FString URLRequestStatus_Canceled(TEXT("CANCELED")); + const static FString URLRequestStatus_Failed(TEXT("FAILED")); + const static FString URLRequestStatus_Unknown(TEXT("UNKNOWN")); + + FString StatusStr; + switch (Status) + { + case CefResourceRequestHandler::URLRequestStatus::UR_SUCCESS: + StatusStr = URLRequestStatus_Success; + break; + case CefResourceRequestHandler::URLRequestStatus::UR_IO_PENDING: + StatusStr = URLRequestStatus_IoPending; + break; + case CefResourceRequestHandler::URLRequestStatus::UR_CANCELED: + StatusStr = URLRequestStatus_Canceled; + break; + case CefResourceRequestHandler::URLRequestStatus::UR_FAILED: + StatusStr = URLRequestStatus_Failed; + break; + case CefResourceRequestHandler::URLRequestStatus::UR_UNKNOWN: + StatusStr = URLRequestStatus_Unknown; + break; + default: + StatusStr = URLRequestStatus_Unknown; + break; + } + return StatusStr; +} + +void FCEFWebInterfaceBrowserWindow::HandleOnBeforeResourceLoad(const CefString& URL, CefRequest::ResourceType Type, FRequestHeaders& AdditionalHeaders, const bool AllowUserCredentials) +{ + BeforeResourceLoadDelegate.ExecuteIfBound(WCHAR_TO_TCHAR(URL.ToWString().c_str()), _ResourceTypeToString(Type), AdditionalHeaders, AllowUserCredentials); +} + +void FCEFWebInterfaceBrowserWindow::HandleOnResourceLoadComplete(const CefString& URL, CefRequest::ResourceType Type, CefResourceRequestHandler::URLRequestStatus Status, int64 ContentLength) +{ + ResourceLoadCompleteDelegate.ExecuteIfBound(WCHAR_TO_TCHAR(URL.ToWString().c_str()), _ResourceTypeToString(Type), _URLRequestStatusToString(Status), ContentLength); +} + +EWebInterfaceBrowserConsoleLogSeverity CefLogSeverityToWebBrowser(cef_log_severity_t Level) +{ + switch (Level) + { + case LOGSEVERITY_VERBOSE: + return EWebInterfaceBrowserConsoleLogSeverity::Verbose; + //case LOGSEVERITY_DEBUG: // same as LOGSEVERITY_VERBOSE + // return Verbose; + case LOGSEVERITY_INFO: + return EWebInterfaceBrowserConsoleLogSeverity::Info; + case LOGSEVERITY_WARNING: + return EWebInterfaceBrowserConsoleLogSeverity::Warning; + case LOGSEVERITY_ERROR: + return EWebInterfaceBrowserConsoleLogSeverity::Error; + case LOGSEVERITY_FATAL: + return EWebInterfaceBrowserConsoleLogSeverity::Fatal; + case LOGSEVERITY_DEFAULT: + default: + return EWebInterfaceBrowserConsoleLogSeverity::Default; + } +} + +void FCEFWebInterfaceBrowserWindow::HandleOnConsoleMessage(CefRefPtr<CefBrowser> Browser, cef_log_severity_t Level, const CefString& Message, const CefString& Source, int32 Line) +{ + ConsoleMessageDelegate.ExecuteIfBound(WCHAR_TO_TCHAR(Message.ToWString().c_str()), WCHAR_TO_TCHAR(Source.ToWString().c_str()), Line, CefLogSeverityToWebBrowser(Level)); +} + +TOptional<FString> FCEFWebInterfaceBrowserWindow::GetResourceContent( CefRefPtr< CefFrame > Frame, CefRefPtr< CefRequest > Request) +{ + if (ContentsToLoad.IsSet()) + { + FString Contents = ContentsToLoad.GetValue(); + ContentsToLoad.Reset(); + return Contents; + } + if (OnLoadUrl().IsBound()) + { + FString Method = WCHAR_TO_TCHAR(Request->GetMethod().ToWString().c_str()); + FString Url = WCHAR_TO_TCHAR(Request->GetURL().ToWString().c_str()); + FString Response; + if ( OnLoadUrl().Execute(Method, Url, Response)) + { + return Response; + } + } + + return TOptional<FString>(); +} + + +int32 FCEFWebInterfaceBrowserWindow::GetCefKeyboardModifiers(const FKeyEvent& KeyEvent) +{ + int32 Modifiers = GetCefInputModifiers(KeyEvent); + + const FKey Key = KeyEvent.GetKey(); + if (Key == EKeys::LeftAlt || + Key == EKeys::LeftCommand || + Key == EKeys::LeftControl || + Key == EKeys::LeftShift) + { + Modifiers |= EVENTFLAG_IS_LEFT; + } + if (Key == EKeys::RightAlt || + Key == EKeys::RightCommand || + Key == EKeys::RightControl || + Key == EKeys::RightShift) + { + Modifiers |= EVENTFLAG_IS_RIGHT; + } + if (Key == EKeys::NumPadZero || + Key == EKeys::NumPadOne || + Key == EKeys::NumPadTwo || + Key == EKeys::NumPadThree || + Key == EKeys::NumPadFour || + Key == EKeys::NumPadFive || + Key == EKeys::NumPadSix || + Key == EKeys::NumPadSeven || + Key == EKeys::NumPadEight || + Key == EKeys::NumPadNine) + { + Modifiers |= EVENTFLAG_IS_KEY_PAD; + } + + return Modifiers; +} + +int32 FCEFWebInterfaceBrowserWindow::GetCefMouseModifiers(const FPointerEvent& InMouseEvent) +{ + int32 Modifiers = GetCefInputModifiers(InMouseEvent); + + if (InMouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) + { + Modifiers |= EVENTFLAG_LEFT_MOUSE_BUTTON; + } + if (InMouseEvent.IsMouseButtonDown(EKeys::MiddleMouseButton)) + { + Modifiers |= EVENTFLAG_MIDDLE_MOUSE_BUTTON; + } + if (InMouseEvent.IsMouseButtonDown(EKeys::RightMouseButton)) + { + Modifiers |= EVENTFLAG_RIGHT_MOUSE_BUTTON; + } + + return Modifiers; +} + +CefMouseEvent FCEFWebInterfaceBrowserWindow::GetCefMouseEvent(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + CefMouseEvent Event; + + FGeometry MouseGeometry = MyGeometry; + if (bUsingAcceleratedPaint) + { + // undo the texture flip if we are using accelerated rendering + MouseGeometry = MyGeometry.MakeChild(FSlateRenderTransform(FScale2D(1, -1))); + } + + float DPIScale = MouseGeometry.Scale; + if (TSharedPtr<SWindow> ParentWindowPtr = ParentWindow.Pin()) + { + DPIScale /= ParentWindowPtr->GetNativeWindow()->GetDPIScaleFactor(); + } + + + FVector2D LocalPos = MouseGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition()) * DPIScale; + if (bIsPopup) + { + LocalPos += PopupPosition; + } + Event.x = LocalPos.X; + Event.y = LocalPos.Y; + Event.modifiers = GetCefMouseModifiers(MouseEvent); + return Event; +} + +int32 FCEFWebInterfaceBrowserWindow::GetCefInputModifiers(const FInputEvent& InputEvent) +{ + int32 Modifiers = 0; + + if (InputEvent.IsShiftDown()) + { + Modifiers |= EVENTFLAG_SHIFT_DOWN; + } + if (InputEvent.IsControlDown()) + { +#if PLATFORM_MAC + // Slate swaps the flags for Command and Control on OSX, so we need to swap them back for CEF + Modifiers |= EVENTFLAG_COMMAND_DOWN; +#else + Modifiers |= EVENTFLAG_CONTROL_DOWN; +#endif + } + if (InputEvent.IsAltDown()) + { + Modifiers |= EVENTFLAG_ALT_DOWN; + } + if (InputEvent.IsCommandDown()) + { +#if PLATFORM_MAC + // Slate swaps the flags for Command and Control on OSX, so we need to swap them back for CEF + Modifiers |= EVENTFLAG_CONTROL_DOWN; +#else + Modifiers |= EVENTFLAG_COMMAND_DOWN; +#endif + } + if (InputEvent.AreCapsLocked()) + { + Modifiers |= EVENTFLAG_CAPS_LOCK_ON; + } + + return Modifiers; +} + +bool FCEFWebInterfaceBrowserWindow::CanSupportAcceleratedPaint() +{ + static bool DisableAcceleratedPaint = FParse::Param(FCommandLine::Get(), TEXT("nocefaccelpaint")); + if (DisableAcceleratedPaint) + { + return false; + } + + static bool ForceAcceleratedPaint = FParse::Param(FCommandLine::Get(), TEXT("forcecefaccelpaint")); + if (ForceAcceleratedPaint) + { + return true; + } + + // Use off screen rendering so we can integrate with our windows +#if PLATFORM_LINUX + return false; +#elif PLATFORM_WINDOWS +#if PLATFORM_64BITS + static bool Windows10OrAbove = FWindowsPlatformMisc::VerifyWindowsVersion(10, 0); //Win10 + if (Windows10OrAbove == false) + { + return false; + } + + // match the logic in GetStandardStandaloneRenderer() from StandaloneRenderer.cpp to check for the OGL slate renderer + if (FParse::Param(FCommandLine::Get(), TEXT("opengl"))) + { + return false; + } + + return true; +#else + return false; // 32-bit windows doesn't have the accelerated rendering patches applied, it can be done if needed +#endif +#elif PLATFORM_MAC + return false; // Needs RHI support for the CreateSharedHandleTexture call +#else + return false; +#endif + +} + +void FCEFWebInterfaceBrowserWindow::UpdateCachedGeometry(const FGeometry& AllottedGeometry) +{ +#if !PLATFORM_LINUX + // Forward along the geometry for use by IME + Ime->UpdateCachedGeometry(AllottedGeometry); +#endif + if (RHIRenderHelper) + { + RHIRenderHelper->UpdateCachedGeometry(AllottedGeometry); + } +} + +void FCEFWebInterfaceBrowserWindow::CheckTickActivity() +{ + // Early out if we're currently hidden, not initialized or currently loading. + if (bIsHidden || !IsValid() || IsLoading() || ViewportSize == FIntPoint::ZeroValue) + { + return; + } + + // We clear the bTickedLastFrame flag here and set it on every Slate tick. + // If it's still clear when we come back it means we're not getting ticks from slate. + // Note: The BrowserSingleton object will not invoke this method if Slate itself is sleeping. + // Therefore we can safely assume the widget is hidden in that case. + if (!bTickedLastFrame) + { + SetIsHidden(true); + } + else if(bNeedsResize) + { + bNeedsResize = false; + InternalCefBrowser->GetHost()->WasResized(); + } + + bTickedLastFrame = false; +} + +void FCEFWebInterfaceBrowserWindow::RequestNavigationInternal(FString Url, FString Contents) +{ + if (!IsValid()) + { + return; + } + + CefRefPtr<CefFrame> MainFrame = InternalCefBrowser->GetMainFrame(); + if (MainFrame.get() != nullptr) + { + ContentsToLoad = Contents.IsEmpty() ? TOptional<FString>() : Contents; + PendingLoadUrl = Url; + + if (!bDeferNavigations) + { + ProcessPendingNavigation(); + } + } +} + +bool FCEFWebInterfaceBrowserWindow::HasPendingNavigation() +{ + return !PendingLoadUrl.IsEmpty(); +} + +void FCEFWebInterfaceBrowserWindow::ProcessPendingNavigation() +{ + if (!IsValid() || bDeferNavigations || !HasPendingNavigation()) + { + return; + } + + CefRefPtr<CefFrame> MainFrame = InternalCefBrowser->GetMainFrame(); + if (MainFrame.get() != nullptr) + { + CefString Url = TCHAR_TO_WCHAR(*PendingLoadUrl); + PendingLoadUrl.Empty(); +#if PLATFORM_MAC + if ([NSThread isMainThread]) + { + MainFrame->LoadURL(Url); + } + else + { + MainThreadCall(^{ + MainFrame->LoadURL(Url); + }, NSDefaultRunLoopMode, true); + } +#else + MainFrame->LoadURL(Url); +#endif + } +} + +void FCEFWebInterfaceBrowserWindow::SetIsHidden(bool bValue) +{ + if( bIsHidden == bValue ) + { + return; + } + bIsHidden = bValue; + if ( IsValid() ) + { + CefRefPtr<CefBrowserHost> BrowserHost = InternalCefBrowser->GetHost(); +#if PLATFORM_WINDOWS + HWND NativeWindowHandle = BrowserHost->GetWindowHandle(); + if (NativeWindowHandle != nullptr) + { + // When rendering directly into a subwindow, we must hide the native window when fully obscured + ::ShowWindow(NativeWindowHandle, bIsHidden ? SW_HIDE : SW_SHOW); + + if (bIsHidden ) + { + TSharedPtr<SWindow> ParentWindowPtr = ParentWindow.Pin(); + if (::IsWindowEnabled(NativeWindowHandle) && ParentWindowPtr.IsValid()) + { + ::SetFocus((HWND)ParentWindowPtr->GetNativeWindow()->GetOSWindowHandle()); + } + // when hidden also resize the window to 0x0 to further reduce resource usage. This copies the + // behavior of the CefClient code, see browser_window_std_win.cc in the ::Hide() function. This code + // is also required for the HTML5 visibility API to work + SetWindowPos(NativeWindowHandle, NULL, 0, 0, 0, 0, SWP_NOZORDER | SWP_NOMOVE | SWP_NOACTIVATE); + } + else + { + // restore the window to its right size/position + HWND Parent = ::GetParent(NativeWindowHandle); + // Position is in screen coordinates, so we'll need to get the parent window location first. + RECT ParentRect = { 0, 0, 0, 0 }; + if (Parent) + { + ::GetWindowRect(Parent, &ParentRect); + } + + FIntPoint WindowSizeScaled = (FVector2D(ViewportSize) * ViewportDPIScaleFactor).IntPoint(); + + ::SetWindowPos(NativeWindowHandle, 0, ViewportPos.X - ParentRect.left, ViewportPos.Y - ParentRect.top, WindowSizeScaled.X, WindowSizeScaled.Y, 0); + } + } + else + { +#elif PLATFORM_MAC + CefWindowHandle NativeWindowHandle = BrowserHost->GetWindowHandle(); + if (NativeWindowHandle != nullptr) + { + NSView *browserView = CAST_CEF_WINDOW_HANDLE_TO_NSVIEW(NativeWindowHandle); + if(bIsHidden) + { + [browserView setHidden:YES]; + } + else + { + [browserView setHidden:NO]; + } + + } + else + { +#endif + BrowserHost->WasHidden(bIsHidden); +#if PLATFORM_WINDOWS || PLATFORM_MAC + } +#endif + } +} + +void FCEFWebInterfaceBrowserWindow::SetIsDisabled(bool bValue) +{ + if (bIsDisabled == bValue) + { + return; + } + bIsDisabled = bValue; + SetIsHidden(bIsDisabled); +} + +TSharedPtr<SWindow> FCEFWebInterfaceBrowserWindow::GetParentWindow() const +{ + TSharedPtr<SWindow> ParentWindowPtr = ParentWindow.Pin(); + return ParentWindowPtr; +} + +void FCEFWebInterfaceBrowserWindow::SetParentWindow(TSharedPtr<SWindow> Window) +{ + ParentWindow = Window; +#if PLATFORM_WINDOWS + if (IsValid()) + { + CefRefPtr<CefBrowserHost> BrowserHost = InternalCefBrowser->GetHost(); + + HWND NativeWindowHandle = BrowserHost->GetWindowHandle(); + if (NativeWindowHandle != nullptr) + { + TSharedPtr<SWindow> ParentWindowPtr = ParentWindow.Pin(); + void* ParentWindowHandle = (ParentWindow.IsValid() && ParentWindowPtr->GetNativeWindow().IsValid()) ? ParentWindowPtr->GetNativeWindow()->GetOSWindowHandle() : nullptr; + if (ParentWindowHandle != nullptr) + { + // When rendering directly to a HWND update its parent windown + ::SetParent(NativeWindowHandle, (HWND)ParentWindowHandle); + } + } + } +#endif +} + +CefRefPtr<CefDictionaryValue> FCEFWebInterfaceBrowserWindow::GetProcessInfo() +{ + CefRefPtr<CefDictionaryValue> Retval = nullptr; + if (IsValid()) + { + Retval = CefDictionaryValue::Create(); + Retval->SetInt("browser", InternalCefBrowser->GetIdentifier()); + Retval->SetDictionary("bindings", Scripting->GetPermanentBindings()); + } + return Retval; +} + +bool FCEFWebInterfaceBrowserWindow::OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> frame, CefProcessId SourceProcess, CefRefPtr<CefProcessMessage> Message) +{ + bool bHandled = Scripting->OnProcessMessageReceived(Browser, SourceProcess, Message); + + if (!bHandled) + { +#if PLATFORM_MAC + if (!bInDirectHwndMode) // IME is handled by the CEF control in direct render mode + { + bHandled = Ime->OnProcessMessageReceived(Browser, SourceProcess, Message); + } +#elif PLATFORM_WINDOWS + bHandled = Ime->OnProcessMessageReceived(Browser, SourceProcess, Message); +#endif + } + + return bHandled; +} + +void FCEFWebInterfaceBrowserWindow::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + Scripting->BindUObject(Name, Object, bIsPermanent); +} + +void FCEFWebInterfaceBrowserWindow::UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + Scripting->UnbindUObject(Name, Object, bIsPermanent); +} + +void FCEFWebInterfaceBrowserWindow::BindInputMethodSystem(ITextInputMethodSystem* TextInputMethodSystem) +{ +#if !PLATFORM_LINUX + Ime->BindInputMethodSystem(TextInputMethodSystem); +#endif +} + +void FCEFWebInterfaceBrowserWindow::UnbindInputMethodSystem() +{ +#if !PLATFORM_LINUX + Ime->UnbindInputMethodSystem(); +#endif +} + +void FCEFWebInterfaceBrowserWindow::OnBrowserClosing() +{ + bIsClosing = true; +} + +void FCEFWebInterfaceBrowserWindow::OnBrowserClosed() +{ + if(OnCloseWindow().IsBound()) + { + OnCloseWindow().Execute(TWeakPtr<IWebInterfaceBrowserWindow>(SharedThis(this))); + } + + Scripting->UnbindCefBrowser(); +#if !PLATFORM_LINUX + Ime->UnbindCefBrowser(); +#endif + InternalCefBrowser = nullptr; +} + +void FCEFWebInterfaceBrowserWindow::SetPopupMenuPosition(CefRect CefPopupSize) +{ + // We only store the position, as the size will be provided ib the OnPaint call. + PopupPosition = FIntPoint(CefPopupSize.x, CefPopupSize.y); +} + +void FCEFWebInterfaceBrowserWindow:: ShowPopupMenu(bool bShow) +{ + if (bShow) + { + bShowPopupRequested = true; // We have to delay showing the popup until we get the first OnPaint on it. + } + else + { + bPopupHasFocus = false; + bShowPopupRequested = false; + OnDismissPopup().Broadcast(); + } +} + +void FCEFWebInterfaceBrowserWindow::OnImeCompositionRangeChanged( + CefRefPtr<CefBrowser> Browser, + const CefRange& SelectionRange, + const CefRenderHandler::RectList& CharacterBounds) +{ + if (InternalCefBrowser != nullptr && InternalCefBrowser->IsSame(Browser)) + { +#if !PLATFORM_LINUX + Ime->CEFCompositionRangeChanged(SelectionRange, CharacterBounds); +#endif + } +} + +void FCEFWebInterfaceBrowserWindow::UpdateDragRegions(const TArray<FWebInterfaceBrowserDragRegion>& Regions) +{ + DragRegions = Regions; +} + +bool FCEFWebInterfaceBrowserWindow::IsInDragRegion(const FIntPoint& Point) +{ + // Here we traverse the array of drag regions backwards because we assume the drag regions are z ordered such that + // the end of the list contains the drag regions of the top most elements of the web page. We can stop checking + // once we hit a region that contains our point. + for (int32 Idx = DragRegions.Num() - 1; Idx >= 0; --Idx) + { + if (DragRegions[Idx].Rect.Contains(Point)) + { + return DragRegions[Idx].bDraggable; + } + } + return false; +} + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindow.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindow.h new file mode 100644 index 0000000000000000000000000000000000000000..8287f1b9a86572231bc2dc93904626580cf7a588 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindow.h @@ -0,0 +1,789 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFWebBrowserWindow.h + +#pragma once + +#include "CoreMinimal.h" +#include "Input/CursorReply.h" +#include "Input/Events.h" +#include "Input/Reply.h" +#include "Widgets/SViewport.h" +#include "WebInterfaceBrowserSingleton.h" + +#if WITH_CEF3 + +#include "IWebInterfaceBrowserWindow.h" +#include "CEFInterfaceBrowserHandler.h" + + +#include "CEFInterfaceLibCefIncludes.h" + + +#endif + +class FBrowserBufferedVideo; +class FCEFInterfaceBrowserHandler; +class FCEFInterfaceJSScripting; +class FSlateUpdatableTexture; +class IWebInterfaceBrowserPopupFeatures; +class IWebInterfaceBrowserWindow; +struct Rect; +class FSlateShaderResource; +enum class EWebInterfaceBrowserDocumentState; +struct FGeometry; +struct FPointerEvent; +class UObject; +struct FInputEvent; +class FWebJSScripting; +class FCEFInterfaceImeHandler; +class ITextInputMethodSystem; +class FCEFWebInterfaceBrowserWindowRHIHelper; + +#if WITH_CEF3 + +/** + * Helper for containing items required for CEF browser window creation. + */ +struct FWebInterfaceBrowserWindowInfo +{ + FWebInterfaceBrowserWindowInfo(CefRefPtr<CefBrowser> InBrowser, CefRefPtr<FCEFInterfaceBrowserHandler> InHandler) + : Browser(InBrowser) + , Handler(InHandler) + {} + CefRefPtr<CefBrowser> Browser; + CefRefPtr<FCEFInterfaceBrowserHandler> Handler; +}; + +/** +* Representation of a window drag region. +*/ +struct FWebInterfaceBrowserDragRegion +{ + FWebInterfaceBrowserDragRegion(const FIntRect& InRect, bool bInDraggable) + : Rect(InRect) + , bDraggable(bInDraggable) + {} + + FIntRect Rect; + bool bDraggable; +}; + +/** + * Implementation of interface for dealing with a Web Browser window. + */ +class FCEFWebInterfaceBrowserWindow + : public IWebInterfaceBrowserWindow + , public TSharedFromThis<FCEFWebInterfaceBrowserWindow> +{ + // Allow the Handler to access functions only it needs + friend class FCEFInterfaceBrowserHandler; + + // The WebBrowserSingleton should be the only one creating instances of this class + friend class FWebInterfaceBrowserSingleton; + + // CreateWidget should only be called by the WebBrowserView + friend class SWebInterfaceBrowserView; + +private: + /** + * Creates and initializes a new instance. + * + * @param Browser The CefBrowser object representing this browser window. + * @param Handler Pointer to the CEF handler for this window. + * @param Url The Initial URL that will be loaded. + * @param ContentsToLoad Optional string to load as a web page. + * @param bShowErrorMessage Whether to show an error message in case of loading errors. + * @param bThumbMouseButtonNavigation Whether to allow forward and back navigation via the mouse thumb buttons. + * @param bUseTransparency Whether to enable transparency. + * @param bJSBindingToLoweringEnabled Whether we ToLower all JavaScript member names. + */ + FCEFWebInterfaceBrowserWindow(CefRefPtr<CefBrowser> Browser, CefRefPtr<FCEFInterfaceBrowserHandler> Handler, FString Url, TOptional<FString> ContentsToLoad, bool bShowErrorMessage, bool bThumbMouseButtonNavigation, bool bUseTransparency, bool bInUseNativeCursors, bool bJSBindingToLoweringEnabled, bool bUsingAcceleratedPaint); + + /** + * Create the SWidget for this WebBrowserWindow + */ + TSharedRef<SViewport> CreateWidget(); + +public: + /** Virtual Destructor. */ + virtual ~FCEFWebInterfaceBrowserWindow(); + + bool IsShowingErrorMessages() const { return bShowErrorMessage; } + bool IsThumbMouseButtonNavigationEnabled() const { return bThumbMouseButtonNavigation; } + bool UseTransparency() const { return bUseTransparency; } + bool UsingAcceleratedPaint() const { return bUsingAcceleratedPaint; } + bool UseNativeCursors() { return bUseNativeCursors; } + +public: + + // IWebBrowserWindow Interface + + virtual void LoadURL(FString NewURL) override; + virtual void LoadString(FString Contents, FString DummyURL) override; + virtual void SetViewportSize(FIntPoint WindowSize, FIntPoint WindowPos) override; + virtual FIntPoint GetViewportSize() const override { return FIntPoint::NoneValue; } + virtual FSlateShaderResource* GetTexture(bool bIsPopup = false) override; + virtual bool IsValid() const override; + virtual bool IsInitialized() const override; + virtual bool IsClosing() const override; + virtual EWebInterfaceBrowserDocumentState GetDocumentLoadingState() const override; + virtual FString GetTitle() const override; + virtual FString GetUrl() const override; + virtual void GetSource(TFunction<void (const FString&)> Callback) const override; + virtual bool OnKeyDown(const FKeyEvent& InKeyEvent) override; + virtual bool OnKeyUp(const FKeyEvent& InKeyEvent) override; + virtual bool OnKeyChar(const FCharacterEvent& InCharacterEvent) override; + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual FReply OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; + virtual void SetSupportsMouseWheel(bool bValue) override; + virtual bool GetSupportsMouseWheel() const override; + virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual FReply OnTouchGesture(const FGeometry& MyGeometry, const FPointerEvent& GestureEvent, bool bIsPopup) override; + virtual void OnFocus(bool SetFocus, bool bIsPopup) override; + virtual void OnCaptureLost() override; + virtual bool CanGoBack() const override; + virtual void GoBack() override; + virtual bool CanGoForward() const override; + virtual void GoForward() override; + virtual bool IsLoading() const override; + virtual void Reload() override; + virtual void StopLoad() override; + virtual void ExecuteJavascript(const FString& Script) override; + virtual void CloseBrowser(bool bForce, bool bBlockTillClosed) override; + virtual void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) override; + virtual void UnbindUObject(const FString& Name, UObject* Object = nullptr, bool bIsPermanent = true) override; + virtual void BindInputMethodSystem(ITextInputMethodSystem* TextInputMethodSystem) override; + virtual void UnbindInputMethodSystem() override; + virtual int GetLoadError() override; + virtual void SetIsDisabled(bool bValue) override; + virtual TSharedPtr<SWindow> GetParentWindow() const override; + virtual void SetParentWindow(TSharedPtr<SWindow> Window) override; + + DECLARE_DERIVED_EVENT(FCEFWebInterfaceBrowserWindow, IWebInterfaceBrowserWindow::FOnDocumentStateChanged, FOnDocumentStateChanged); + virtual FOnDocumentStateChanged& OnDocumentStateChanged() override + { + return DocumentStateChangedEvent; + } + + DECLARE_DERIVED_EVENT(FCEFWebInterfaceBrowserWindow, IWebInterfaceBrowserWindow::FOnTitleChanged, FOnTitleChanged); + virtual FOnTitleChanged& OnTitleChanged() override + { + return TitleChangedEvent; + } + + DECLARE_DERIVED_EVENT(FCEFWebInterfaceBrowserWindow, IWebInterfaceBrowserWindow::FOnUrlChanged, FOnUrlChanged); + virtual FOnUrlChanged& OnUrlChanged() override + { + return UrlChangedEvent; + } + + DECLARE_DERIVED_EVENT(FCEFWebInterfaceBrowserWindow, IWebInterfaceBrowserWindow::FOnToolTip, FOnToolTip); + virtual FOnToolTip& OnToolTip() override + { + return ToolTipEvent; + } + + DECLARE_DERIVED_EVENT(FCEFWebInterfaceBrowserWindow, IWebInterfaceBrowserWindow::FOnNeedsRedraw, FOnNeedsRedraw); + virtual FOnNeedsRedraw& OnNeedsRedraw() override + { + return NeedsRedrawEvent; + } + + virtual FOnBeforeBrowse& OnBeforeBrowse() override + { + return BeforeBrowseDelegate; + } + + virtual FOnLoadUrl& OnLoadUrl() override + { + return LoadUrlDelegate; + } + + virtual FOnCreateWindow& OnCreateWindow() override + { + return WebBrowserHandler->OnCreateWindow(); + } + + virtual FOnCloseWindow& OnCloseWindow() override + { + return CloseWindowDelegate; + } + + virtual FCursorReply OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) override + { + return Cursor == EMouseCursor::Default ? FCursorReply::Unhandled() : FCursorReply::Cursor(Cursor); + } + + virtual FOnBeforePopupDelegate& OnBeforePopup() override + { + return WebBrowserHandler->OnBeforePopup(); + } + + virtual FOnBeforeResourceLoadDelegate& OnBeforeResourceLoad() override + { + if (!WebBrowserHandler->OnBeforeResourceLoad().IsBoundToObject(this)) + { + WebBrowserHandler->OnBeforeResourceLoad().BindSP(this, &FCEFWebInterfaceBrowserWindow::HandleOnBeforeResourceLoad); + } + + return BeforeResourceLoadDelegate; + } + + virtual FOnResourceLoadCompleteDelegate& OnResourceLoadComplete() override + { + if (!WebBrowserHandler->OnResourceLoadComplete().IsBoundToObject(this)) + { + WebBrowserHandler->OnResourceLoadComplete().BindSP(this, &FCEFWebInterfaceBrowserWindow::HandleOnResourceLoadComplete); + } + + return ResourceLoadCompleteDelegate; + } + + virtual FOnConsoleMessageDelegate& OnConsoleMessage() override + { + if (!WebBrowserHandler->OnConsoleMessage().IsBoundToObject(this)) + { + WebBrowserHandler->OnConsoleMessage().BindSP(this, &FCEFWebInterfaceBrowserWindow::HandleOnConsoleMessage); + } + + return ConsoleMessageDelegate; + } + + DECLARE_DERIVED_EVENT(FCEFWebInterfaceBrowserWindow, IWebInterfaceBrowserWindow::FOnShowPopup, FOnShowPopup); + virtual FOnShowPopup& OnShowPopup() override + { + return ShowPopupEvent; + } + + DECLARE_DERIVED_EVENT(FCEFWebInterfaceBrowserWindow, IWebInterfaceBrowserWindow::FOnDismissPopup, FOnDismissPopup); + virtual FOnDismissPopup& OnDismissPopup() override + { + return DismissPopupEvent; + } + + virtual FOnShowDialog& OnShowDialog() override + { + return ShowDialogDelegate; + } + + virtual FOnDismissAllDialogs& OnDismissAllDialogs() override + { + return DismissAllDialogsDelegate; + } + + virtual FOnSuppressContextMenu& OnSuppressContextMenu() override + { + return SuppressContextMenuDelgate; + } + + virtual FOnDragWindow& OnDragWindow() override + { + return DragWindowDelegate; + } + + virtual FOnUnhandledKeyDown& OnUnhandledKeyDown() override + { + return UnhandledKeyDownDelegate; + } + + virtual FOnUnhandledKeyUp& OnUnhandledKeyUp() override + { + return UnhandledKeyUpDelegate; + } + + virtual FOnUnhandledKeyChar& OnUnhandledKeyChar() override + { + return UnhandledKeyCharDelegate; + } + +private: + + /** + * Used to obtain the internal CEF browser. + * + * @return The bound CEF browser. + */ + CefRefPtr<CefBrowser> GetCefBrowser(); + + /** + * Sets the Title of this window. + * + * @param InTitle The new title of this window. + */ + void SetTitle(const CefString& InTitle); + + /** + * Sets the url of this window. + * + * @param InUrl The new url of this window. + */ + void SetUrl(const CefString& InUrl); + + /** + * Sets the tool tip for this window. + * + * @param InToolTip The text to show in the ToolTip. Empty string for no tool tip. + */ + void SetToolTip(const CefString& InToolTip); + + /** + * Get the current proportions of this window. + * + * @param Rect Reference to CefRect to store sizes. + * @return Whether Rect was set up correctly. + */ + void GetViewRect(CefRect& Rect); + + /** Notifies when document loading has failed. */ + void NotifyDocumentError(CefLoadHandler::ErrorCode InErrorCode, const CefString& ErrorText, const CefString& FailedUrl); + + /** Notifies clients that document loading has failed. */ + void NotifyDocumentError(int ErrorCode); + + /** + * Notifies clients that the loading state of the document has changed. + * + * @param IsLoading Whether the document is loading (false = completed). + */ + void NotifyDocumentLoadingStateChange(bool IsLoading); + + /** + * Called when there is an update to the rendered web page. + * + * @param Type Paint type. + * @param DirtyRects List of image areas that have been changed. + * @param Buffer Pointer to the raw texture data. + * @param Width Width of the texture. + * @param Height Height of the texture. + */ + void OnPaint(CefRenderHandler::PaintElementType Type, const CefRenderHandler::RectList& DirtyRects, const void* Buffer, int Width, int Height); + + /** + * Called when there is an update to the rendered web page. + * + * @param Type Paint type. + * @param DirtyRects List of image areas that have been changed. + * @param SharedHandle the handle for a D3D11 Texture2D that can be accessed via ID3D11Device using the OpenSharedResource method + */ + void OnAcceleratedPaint(CefRenderHandler::PaintElementType type, const CefRenderHandler::RectList& DirtyRects, void* SharedHandle); + + /** + * Called when cursor would change due to web browser interaction. + * + * @param Cursor Handle to CEF mouse cursor. + */ + bool OnCursorChange(CefCursorHandle Cursor, cef_cursor_type_t Type, const CefCursorInfo& CustomCursorInfo); + + /** + * Called when a message was received from the renderer process. + * + * @param Browser The CefBrowser for this window. + * @param SourceProcess The process id of the sender of the message. Currently always PID_RENDERER. + * @param Message The actual message. + * @return true if the message was handled, else false. + */ + bool OnProcessMessageReceived(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> frame, CefProcessId SourceProcess, CefRefPtr<CefProcessMessage> Message); + + /** + * Called before browser navigation. + * + * @param Browser The CefBrowser for this window. + * @param Frame The CefFrame the request came from. + * @param Request The CefRequest containing web request info. + * @param user_gesture true if the navigation was a result of a gesture, false otherwise. + * @param bIsRedirect true if the navigation was a result of redirection, false otherwise. + * @return true if the navigation was handled and no further processing of the navigation request should be disabled, false if the navigation should be handled by the default CEF implementation. + */ + bool OnBeforeBrowse(CefRefPtr<CefBrowser> Browser, CefRefPtr<CefFrame> Frame, CefRefPtr<CefRequest> Request, bool user_gesture, bool bIsRedirect); + + void HandleOnBeforeResourceLoad(const CefString& URL, CefRequest::ResourceType Type, FRequestHeaders& AdditionalHeaders, const bool AllowUserCredentials); + void HandleOnResourceLoadComplete(const CefString& URL, CefRequest::ResourceType Type, CefResourceRequestHandler::URLRequestStatus Status, int64 ContentLength); + void HandleOnConsoleMessage(CefRefPtr<CefBrowser> Browser, cef_log_severity_t Level, const CefString& Message, const CefString& Source, int32 Line); + + /** + * Called before loading a resource to allow overriding the content for a request. + * + * @return string content representing the content to show for the URL or an unset value to fetch the URL normally. + */ + TOptional<FString> GetResourceContent( CefRefPtr< CefFrame > Frame, CefRefPtr< CefRequest > Request); + + /** + * Convenience function to translate modifier keys from Cef keyboard event + */ + FModifierKeysState SlateModifiersFromCefModifiers(const CefKeyEvent& CefEvent); + + /** + * Called when browser reports a key event that was not handled by it + */ + bool OnUnhandledKeyEvent(const CefKeyEvent& CefEvent); + + /** + * Handle showing javascript dialogs + */ + bool OnJSDialog(CefJSDialogHandler::JSDialogType DialogType, const CefString& MessageText, const CefString& DefaultPromptText, CefRefPtr<CefJSDialogCallback> Callback, bool& OutSuppressMessage); + + /** + * Handle showing unload confirmation dialogs + */ + bool OnBeforeUnloadDialog(const CefString& MessageText, bool IsReload, CefRefPtr<CefJSDialogCallback> Callback); + + /** + * Notify when any and all pending dialogs should be canceled + */ + void OnResetDialogState(); + + /** + * Called when render process was terminated abnormally. + */ + void OnRenderProcessTerminated(CefRequestHandler::TerminationStatus Status); + + + /** Called when the browser requests a new UI window + * + * @param NewBrowserWindow The web browser window to display in the new UI window. + * @param BrowserPopupFeatures The popup features and settings for the browser window. + * @return true if the UI window was created, false otherwise. + */ + bool RequestCreateWindow(const TSharedRef<IWebInterfaceBrowserWindow>& NewBrowserWindow, const TSharedPtr<IWebInterfaceBrowserPopupFeatures>& BrowserPopupFeatures); + + //bool SupportsCloseWindows(); + //bool RequestCloseWindow(const TSharedRef<IWebInterfaceBrowserWindow>& BrowserWindow); + + + /** + * Called once the browser begins closing procedures. + */ + void OnBrowserClosing(); + + /** + * Called once the browser is closed. + */ + void OnBrowserClosed(); + + /** + * Called to set the popup menu location. Note that CEF also passes a size to this method, + * which is ignored as the correct size is usually not known until inside OnPaint. + * + * @param PopupSize The location of the popup widget. + */ + void SetPopupMenuPosition(CefRect PopupSize); + + /** + * Called to request that the popup widget is shown or hidden. + * + * @param bShow true for showing the popup, false for hiding it. + */ + void ShowPopupMenu(bool bShow); + + /** + * Called when the IME composition DOM node has changed. + * @param Browser The CefBrowser for this window. + * @param SelectionRange The range of characters that have been selected. + * @param CharacterBounds The bounds of each character in view coordinates. + */ + void OnImeCompositionRangeChanged( + CefRefPtr<CefBrowser> Browser, + const CefRange& SelectionRange, + const CefRenderHandler::RectList& CharacterBounds); + + void UpdateDragRegions(const TArray<FWebInterfaceBrowserDragRegion>& Regions); + +public: + + /** + * Gets the Cef Keyboard Modifiers based on a Key Event. + * + * @param KeyEvent The Key event. + * @return Bits representing keyboard modifiers. + */ + static int32 GetCefKeyboardModifiers(const FKeyEvent& KeyEvent); + + /** + * Gets the Cef Mouse Modifiers based on a Mouse Event. + * + * @param InMouseEvent The Mouse event. + * @return Bits representing mouse modifiers. + */ + static int32 GetCefMouseModifiers(const FPointerEvent& InMouseEvent); + + /** + * Gets the Cef Input Modifiers based on an Input Event. + * + * @param InputEvent The Input event. + * @return Bits representing input modifiers. + */ + static int32 GetCefInputModifiers(const FInputEvent& InputEvent); + + + /** + * Is this platform able to support the accelerated paint path for CEF. + * + * @return true if supported AND enabled on this platform, false otherwise. + */ + static bool CanSupportAcceleratedPaint(); + +public: + + /** + * Called from the WebBrowserViewport tick event. Allows us to cache the geometry and use it for coordinate transformations. + */ + void UpdateCachedGeometry(const FGeometry& AllottedGeometry); + + /** + * Called from the WebBrowserSingleton tick event. Should test wether the widget got a tick from Slate last frame and set the state to hidden if not. + */ + void CheckTickActivity() override; + + /** + * Called from the engine tick. + */ + void UpdateVideoBuffering(); + + /** + * Called on every browser window when CEF launches a new render process. + * Used to ensure global JS objects are registered as soon as possible. + */ + CefRefPtr<CefDictionaryValue> GetProcessInfo(); + + /** + * Return true if this URL will support adding an Authorization header to it + */ + bool URLRequestAllowsCredentials(const FString& URL) const { return WebBrowserHandler->URLRequestAllowsCredentials(URL); } + +private: + + /** @return the currently valid renderer, if available */ + FSlateRenderer* const GetRenderer(); + + /**Checks whether an error with the renderer occurred and handles it if one has */ + void HandleRenderingError(); + + /** Releases the updatable textures */ + void ReleaseTextures(); + + /** Creates the initial updatable textures */ + bool CreateInitialTextures(); + + /** Executes or defers a LoadUrl navigation */ + void RequestNavigationInternal(FString Url, FString Contents); + + /** Specifies whether or not we have a pending deferred navigation */ + bool HasPendingNavigation(); + + /** Executes navigation on a pending deferred navigation */ + void ProcessPendingNavigation(); + + /** Helper that calls WasHidden on the CEF host object when the value changes */ + void SetIsHidden(bool bValue); + + /** Used by the key down and up handlers to convert Slate key events to the CEF equivalent. */ + void PopulateCefKeyEvent(const FKeyEvent& InKeyEvent, CefKeyEvent& OutKeyEvent); + + /** Used to convert a FPointerEvent to a CefMouseEvent */ + CefMouseEvent GetCefMouseEvent(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup); + + /** Specifies whether or not a point falls within any tagged drag regions that are draggable. */ + bool IsInDragRegion(const FIntPoint& Point); + + /** Used to let us correctly render the web texture for the accelerated render path */ + TOptional<FSlateRenderTransform> GetWebBrowserRenderTransform() const; + + bool BlockInputInDirectHwndMode() const; + +#if PLATFORM_WINDOWS + /** manually load cursor icons from the CEF3 dll if needed, working around a CEF3 bug */ + bool LoadCustomCEF3Cursor(cef_cursor_type_t Type); +#endif +private: + + /** Current state of the document being loaded. */ + EWebInterfaceBrowserDocumentState DocumentState; + + /** Interface to the texture we are rendering to. */ + FSlateUpdatableTexture* UpdatableTextures[2]; + + /** Pointer to the CEF Browser for this window. */ + CefRefPtr<CefBrowser> InternalCefBrowser; + + /** Pointer to the CEF handler for this window. */ + CefRefPtr<FCEFInterfaceBrowserHandler> WebBrowserHandler; + + /** Current title of this window. */ + FString Title; + + /** Current Url of this window. */ + FString CurrentUrl; + + /** Current tool tip. */ + FString ToolTipText; + + /** Current size of this window. */ + FIntPoint ViewportSize; + + /** Current position of this window. */ + FIntPoint ViewportPos; + + /** Current DPI scale factor of this window. */ + float ViewportDPIScaleFactor; + + /** Whether this window is closing. */ + bool bIsClosing; + + /** Whether this window has been painted at least once. */ + bool bIsInitialized; + + /** Optional text to load as a web page. */ + TOptional<FString> ContentsToLoad; + + /** Delegate for broadcasting load state changes. */ + FOnDocumentStateChanged DocumentStateChangedEvent; + + /** Whether to show an error message in case of loading errors. */ + bool bShowErrorMessage; + + /** Whether to allow forward and back navigation via the mouse thumb buttons. */ + bool bThumbMouseButtonNavigation; + + /** Whether transparency is enabled. */ + bool bUseTransparency; + + /** Whether the accelerated paint path is enabled (i.e shared texture handles) */ + bool bUsingAcceleratedPaint; + + /** Whether native cursors are enabled. */ + bool bUseNativeCursors; + + /** Delegate for broadcasting title changes. */ + FOnTitleChanged TitleChangedEvent; + + /** Delegate for broadcasting address changes. */ + FOnUrlChanged UrlChangedEvent; + + /** Delegate for showing or hiding tool tips. */ + FOnToolTip ToolTipEvent; + + /** Delegate for notifying that the window needs refreshing. */ + FOnNeedsRedraw NeedsRedrawEvent; + + /** Delegate that is executed prior to browser navigation. */ + FOnBeforeBrowse BeforeBrowseDelegate; + + /** Delegate for overriding Url contents. */ + FOnLoadUrl LoadUrlDelegate; + + /** Delegate for handling requests to close new windows that were created. */ + FOnCloseWindow CloseWindowDelegate; + + /** Delegate for handling resource load requests */ + FOnBeforeResourceLoadDelegate BeforeResourceLoadDelegate; + + /** Delegate that allows for responses to resource loads */ + FOnResourceLoadCompleteDelegate ResourceLoadCompleteDelegate; + + /** Delegate that allows for response to console logs. Typically used to capture and mirror web logs in client application logs. */ + FOnConsoleMessageDelegate ConsoleMessageDelegate; + + /** Delegate for handling requests to show the popup menu. */ + FOnShowPopup ShowPopupEvent; + + /** Delegate for handling requests to dismiss the current popup menu. */ + FOnDismissPopup DismissPopupEvent; + + /** Delegate for showing dialogs. */ + FOnShowDialog ShowDialogDelegate; + + /** Delegate for dismissing all dialogs. */ + FOnDismissAllDialogs DismissAllDialogsDelegate; + + /** Delegate for suppressing context menu */ + FOnSuppressContextMenu SuppressContextMenuDelgate; + + /** Delegate that is executed when a drag event is detected in an area of the web page tagged as a drag region. */ + FOnDragWindow DragWindowDelegate; + + /** Delegate for handling key down events not handled by the browser. */ + FOnUnhandledKeyDown UnhandledKeyDownDelegate; + + /** Delegate for handling key up events not handled by the browser. */ + FOnUnhandledKeyUp UnhandledKeyUpDelegate; + + /** Delegate for handling key char events not handled by the browser. */ + FOnUnhandledKeyChar UnhandledKeyCharDelegate; + + /** Tracks the current mouse cursor */ + EMouseCursor::Type Cursor; + + /** Tracks whether the widget is currently disabled or not*/ + bool bIsDisabled; + + /** Tracks whether the widget is currently hidden or not*/ + bool bIsHidden; + + /** Used to detect when the widget is hidden*/ + bool bTickedLastFrame; + + /** Tracks whether the widget has been resized and needs to be refreshed */ + bool bNeedsResize; + + /** Tracks whether or not the user initiated a window drag by clicking on a page's drag region. */ + bool bDraggingWindow; + + /** Used for unhandled key events forwarding*/ + TOptional<FKeyEvent> PreviousKeyDownEvent; + TOptional<FKeyEvent> PreviousKeyUpEvent; + TOptional<FCharacterEvent> PreviousCharacterEvent; + bool bIgnoreKeyDownEvent; + bool bIgnoreKeyUpEvent; + bool bIgnoreCharacterEvent; + + /** Used to ignore any popup menus when forwarding focus gained/lost events*/ + bool bMainHasFocus; + bool bPopupHasFocus; + + bool bSupportsMouseWheel; + + FIntPoint PopupPosition; + bool bShowPopupRequested; + + /** This is set to true when reloading after render process crash. */ + bool bRecoverFromRenderProcessCrash; + + int ErrorCode; + + /** Used to defer navigations */ + bool bDeferNavigations; + + /** Used to identify a navigation that needs to fully abort before we can stop deferring navigations. */ + FString PendingAbortUrl; + + /** Used to store the url of pending navigation requests while we need to defer navigations. */ + FString PendingLoadUrl; + + TUniquePtr<FBrowserBufferedVideo> BufferedVideo; +#if PLATFORM_MAC + void *LastPaintedSharedHandle; +#endif + + /** Handling of passing and marshalling messages for JS integration is delegated to a helper class*/ + TSharedPtr<FCEFInterfaceJSScripting> Scripting; + +#if !PLATFORM_LINUX + /** Handling of foreign language character input is delegated to a helper class */ + TSharedPtr<FCEFInterfaceImeHandler> Ime; +#endif + + TArray<FWebInterfaceBrowserDragRegion> DragRegions; + + TWeakPtr<SWindow> ParentWindow; + FCEFWebInterfaceBrowserWindowRHIHelper* RHIRenderHelper; + +#if PLATFORM_WINDOWS || PLATFORM_MAC + bool bInDirectHwndMode; +#endif +}; + +typedef FCEFWebInterfaceBrowserWindow FWebInterfaceBrowserWindow; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindowRHIHelper.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindowRHIHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8b5b091ffcb9e01747707f71154a409f454ee45c --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindowRHIHelper.cpp @@ -0,0 +1,198 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFWebInterfaceBrowserWindowRHIHelper.cpp + +#include "CEFWebInterfaceBrowserWindowRHIHelper.h" +#include "WebInterfaceBrowserLog.h" + +#if WITH_CEF3 + +#include "CEF/CEFWebInterfaceBrowserWindow.h" +#if WITH_ENGINE +#include "RHI.h" +#if PLATFORM_WINDOWS +#include "Slate/SlateTextures.h" +#endif +#endif + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +#include "d3d11.h" +#include "d3d11_1.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif // PLATFORM_WINDOWS + + +FCEFWebInterfaceBrowserWindowRHIHelper::~FCEFWebInterfaceBrowserWindowRHIHelper() +{ +} + +bool FCEFWebInterfaceBrowserWindowRHIHelper::BUseRHIRenderer() +{ +#if WITH_ENGINE + if (GDynamicRHI != nullptr && FCEFWebInterfaceBrowserWindow::CanSupportAcceleratedPaint()) + { + return RHIGetInterfaceType() == ERHIInterfaceType::D3D11; + } +#endif + return false; +} + +void FCEFWebInterfaceBrowserWindowRHIHelper::UpdateCachedGeometry(const FGeometry& AllottedGeometryIn) +{ + AllottedGeometry = AllottedGeometryIn; +} + +TOptional<FSlateRenderTransform> FCEFWebInterfaceBrowserWindowRHIHelper::GetWebBrowserRenderTransform() const +{ + return FSlateRenderTransform(Concatenate(FScale2D(1, -1), FVector2D(0, AllottedGeometry.GetLocalSize().Y))); +} + +FSlateUpdatableTexture *FCEFWebInterfaceBrowserWindowRHIHelper::CreateTexture(void* SharedHandle) +{ +#if WITH_ENGINE + check(BUseRHIRenderer()); +#if PLATFORM_WINDOWS + ID3D11Device1* dev1 = nullptr; + ID3D11Device* D3D11Device = static_cast<ID3D11Device*>(GDynamicRHI->RHIGetNativeDevice()); + HRESULT Hr = D3D11Device->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(&dev1)); + if (FAILED(Hr)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::CreateTexture() - - ID3D11Device::QueryInterface")); + return nullptr; + } + + ID3D11Texture2D* tex = nullptr; + Hr = dev1->OpenSharedResource1(SharedHandle, __uuidof(ID3D11Texture2D), (void**)(&tex)); + if (FAILED(Hr)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::CreateTexture() - - ID3D11Device::OpenSharedResource")); + return nullptr; + } + + D3D11_TEXTURE2D_DESC TexDesc; + tex->GetDesc(&TexDesc); + FSlateTexture2DRHIRef* NewTexture = new FSlateTexture2DRHIRef(TexDesc.Width, TexDesc.Height, PF_R8G8B8A8, nullptr, TexCreate_Dynamic, true); + if (IsInRenderingThread()) + { + NewTexture->InitResource(); + } + else + { + BeginInitResource(NewTexture); + } + + return NewTexture; +#else + return nullptr; +#endif +#else + return nullptr; +#endif +} + +void FCEFWebInterfaceBrowserWindowRHIHelper::UpdateSharedHandleTexture(void *SharedHandle, FSlateUpdatableTexture* SlateTexture, const FIntRect& DirtyIn) +{ +#if WITH_ENGINE + check(BUseRHIRenderer()); + FIntRect Dirty = DirtyIn; + +#if PLATFORM_WINDOWS + ID3D11Device1* dev1 = nullptr; + ID3D11Device* D3D11Device = static_cast<ID3D11Device*>(GDynamicRHI->RHIGetNativeDevice()); + HRESULT Hr = D3D11Device->QueryInterface(__uuidof(ID3D11Device1), reinterpret_cast<void**>(&dev1)); + if (FAILED(Hr)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::UpdateSharedHandleTexture() - ID3D11Device::QueryInterface"), Hr); + return; + } + + ID3D11Texture2D* Tex = nullptr; + Hr = dev1->OpenSharedResource1(SharedHandle, __uuidof(ID3D11Texture2D), (void**)(&Tex)); + if (FAILED(Hr)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::UpdateSharedHandleTexture() - ID3D11Device::OpenSharedResource"), Hr); + return; + } + + D3D11_TEXTURE2D_DESC TexDesc; + Tex->GetDesc(&TexDesc); + if (SlateTexture->GetSlateResource()->GetWidth() != TexDesc.Width + || SlateTexture->GetSlateResource()->GetHeight() != TexDesc.Height) + { + SlateTexture->ResizeTexture(TexDesc.Width, TexDesc.Height); + Dirty = FIntRect(); + } + + TRefCountPtr<IDXGIKeyedMutex> KeyedMutex; + Hr = Tex->QueryInterface(_uuidof(IDXGIKeyedMutex), (void**)&KeyedMutex); + if (FAILED(Hr)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::UpdateSharedHandleTexture() - ID3D11Texture2D::IDXGIKeyedMutex"), Hr); + return; + } + + ENQUEUE_RENDER_COMMAND(CEFAcceleratedPaint)( + [KeyedMutex, SlateTexture, Dirty, Tex](FRHICommandList& RHICmdList) + { + if (KeyedMutex) + { + if (KeyedMutex->AcquireSync(1, 16) == S_OK) + { + D3D11_BOX Region; + Region.front = 0; + Region.back = 1; + if (Dirty.Area() > 0) + { + Region.left = Dirty.Min.X; + Region.top = Dirty.Min.Y; + Region.right = Dirty.Max.X; + Region.bottom = Dirty.Max.Y; + } + else + { + Region.left = 0; + Region.right = SlateTexture->GetSlateResource()->GetWidth(); + Region.top = 0; + Region.bottom = SlateTexture->GetSlateResource()->GetHeight(); + } + + ID3D11DeviceContext* DX11DeviceContext = nullptr; + ID3D11Device* D3D11Device = static_cast<ID3D11Device*>(GDynamicRHI->RHIGetNativeDevice()); + D3D11Device->GetImmediateContext(&DX11DeviceContext); + check(DX11DeviceContext); + + FSlateTexture2DRHIRef* SlateRHITexture = static_cast<FSlateTexture2DRHIRef*>(SlateTexture); + check(SlateRHITexture); + auto Slate2DRef = SlateRHITexture->GetRHIRef(); + if (Slate2DRef && Slate2DRef.IsValid()) + { + auto Slate2DTexture = Slate2DRef->GetTexture2D(); + if (Slate2DTexture) + { + ID3D11Texture2D* D3DTexture = (ID3D11Texture2D*)Slate2DTexture->GetNativeResource(); + + DX11DeviceContext->CopySubresourceRegion(D3DTexture, 0, Region.left, Region.top, Region.front, Tex, 0, &Region); + } + } + + KeyedMutex->ReleaseSync(0); + } + else + { + UE_LOG(LogWebInterfaceBrowser, Verbose, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::UpdateSharedHandleTexture() - failed getting sync"), E_FAIL); + } + } + else + { + UE_LOG(LogWebInterfaceBrowser, Verbose, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::UpdateSharedHandleTexture() - missing KeyedMutex"), E_FAIL); + return; + } + }); +#else + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::UpdateSharedHandleTexture() - missing implementation")); +#endif // PLATFORM_WINDOWS +#else + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("FCEFWebInterfaceBrowserWindowRHIHelper::UpdateSharedHandleTexture() - unsupported usage, RHI renderer but missing engine")); +#endif // WITH_ENGINE +} + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindowRHIHelper.h b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindowRHIHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..44114b52e7f3728bee1f7c68556ca4fb1c65d160 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/CEF/CEFWebInterfaceBrowserWindowRHIHelper.h @@ -0,0 +1,36 @@ +// Engine/Source/Runtime/WebBrowser/Private/CEF/CEFWebInterfaceBrowserWindowRHIHelper.h + +#pragma once + +#include "CoreMinimal.h" + +#if WITH_CEF3 + +#include "Layout/Geometry.h" + +class FSlateTexture2DRHIRef; +class FSlateUpdatableTexture; + +/** + * Implementation of RHI renderer details for the CEF accelerated rendering path + */ +class FCEFWebInterfaceBrowserWindowRHIHelper +{ + +public: + /** Virtual Destructor. */ + virtual ~FCEFWebInterfaceBrowserWindowRHIHelper(); + + +public: + static bool BUseRHIRenderer(); + FSlateUpdatableTexture* CreateTexture(void *ShareHandle); + void UpdateSharedHandleTexture(void* SharedHandle, FSlateUpdatableTexture* SlateTexture, const FIntRect& DirtyIn); + void UpdateCachedGeometry(const FGeometry& AllottedGeometry); + TOptional<FSlateRenderTransform> GetWebBrowserRenderTransform() const; + +private: + FGeometry AllottedGeometry; +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSScripting.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSScripting.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1c18b3a2b846654c71b0e1cd4e1e76a5c1b985b8 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSScripting.cpp @@ -0,0 +1,654 @@ +// Engine/Source/Runtime/WebBrowser/Private/Native/NativeJSScripting.cpp + +#include "NativeInterfaceJSScripting.h" + + +#include "NativeInterfaceJSStructSerializerBackend.h" +#include "NativeInterfaceJSStructDeserializerBackend.h" +#include "StructSerializer.h" +#include "StructDeserializer.h" +#include "UObject/UnrealType.h" +#include "NativeWebInterfaceBrowserProxy.h" + +namespace NativeInterfaceFuncs +{ + const FString ExecuteMethodCommand = TEXT("ExecuteUObjectMethod"); + + typedef TSharedRef<TJsonWriter<>> FJsonWriterRef; + + template<typename ValueType> void WriteValue(FJsonWriterRef Writer, const FString& Key, const ValueType& Value) + { + Writer->WriteValue(Key, Value); + } + void WriteNull(FJsonWriterRef Writer, const FString& Key) + { + Writer->WriteNull(Key); + } + void WriteArrayStart(FJsonWriterRef Writer, const FString& Key) + { + Writer->WriteArrayStart(Key); + } + void WriteObjectStart(FJsonWriterRef Writer, const FString& Key) + { + Writer->WriteObjectStart(Key); + } + void WriteRaw(FJsonWriterRef Writer, const FString& Key, const FString& Value) + { + Writer->WriteRawJSONValue(Key, Value); + } + template<typename ValueType> void WriteValue(FJsonWriterRef Writer, const int, const ValueType& Value) + { + Writer->WriteValue(Value); + } + void WriteNull(FJsonWriterRef Writer, int) + { + Writer->WriteNull(); + } + void WriteArrayStart(FJsonWriterRef Writer, int) + { + Writer->WriteArrayStart(); + } + void WriteObjectStart(FJsonWriterRef Writer, int) + { + Writer->WriteObjectStart(); + } + void WriteRaw(FJsonWriterRef Writer, int, const FString& Value) + { + Writer->WriteRawJSONValue(Value); + } + + template<typename KeyType> + bool WriteJsParam(FNativeInterfaceJSScriptingRef Scripting, FJsonWriterRef Writer, const KeyType& Key, FWebInterfaceJSParam& Param) + { + switch (Param.Tag) + { + case FWebInterfaceJSParam::PTYPE_NULL: + WriteNull(Writer, Key); + break; + case FWebInterfaceJSParam::PTYPE_BOOL: + WriteValue(Writer, Key, Param.BoolValue); + break; + case FWebInterfaceJSParam::PTYPE_DOUBLE: + WriteValue(Writer, Key, Param.DoubleValue); + break; + case FWebInterfaceJSParam::PTYPE_INT: + WriteValue(Writer, Key, Param.IntValue); + break; + case FWebInterfaceJSParam::PTYPE_STRING: + WriteValue(Writer, Key, *Param.StringValue); + break; + case FWebInterfaceJSParam::PTYPE_OBJECT: + { + if (Param.ObjectValue == nullptr) + { + WriteNull(Writer, Key); + } + else + { + FString ConvertedObject = Scripting->ConvertObject(Param.ObjectValue); + WriteRaw(Writer, Key, ConvertedObject); + } + break; + } + case FWebInterfaceJSParam::PTYPE_STRUCT: + { + FString ConvertedStruct = Scripting->ConvertStruct(Param.StructValue->GetTypeInfo(), Param.StructValue->GetData()); + WriteRaw(Writer, Key, ConvertedStruct); + break; + } + case FWebInterfaceJSParam::PTYPE_ARRAY: + { + WriteArrayStart(Writer, Key); + for(int i=0; i < Param.ArrayValue->Num(); ++i) + { + WriteJsParam(Scripting, Writer, i, (*Param.ArrayValue)[i]); + } + Writer->WriteArrayEnd(); + break; + } + case FWebInterfaceJSParam::PTYPE_MAP: + { + WriteObjectStart(Writer, Key); + for(auto& Pair : *Param.MapValue) + { + WriteJsParam(Scripting, Writer, *Pair.Key, Pair.Value); + } + Writer->WriteObjectEnd(); + break; + } + default: + return false; + } + return true; + } +} + + +FString _GetObjectPostInitScript(const FString& Name, const FString& FullyQualifiedName) +{ + return FString::Printf(TEXT("(function(){document.dispatchEvent(new CustomEvent('%s:ready', {details: %s}));})();"), *Name, *FullyQualifiedName); +} + +void FNativeInterfaceJSScripting::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent ) +{ + const FString ExposedName = GetBindingName(Name, Object); + + FString Converted = ConvertObject(Object); + if (bIsPermanent) + { + // Existing permanent objects must be removed first and each object can only have one permanent binding + if (PermanentUObjectsByName.Contains(ExposedName) || BoundObjects[Object].bIsPermanent) + { + return; + } + + BoundObjects[Object]={true, -1}; + PermanentUObjectsByName.Add(ExposedName, Object); + } + + if(!bLoaded) + { + PageLoaded(); + } + else + { + const FString& EscapedName = ExposedName.ReplaceCharWithEscapedChar(); + FString SetValueScript = FString::Printf(TEXT("window.ue['%s'] = %s;"), *EscapedName, *Converted); + SetValueScript.Append(_GetObjectPostInitScript(EscapedName, FString::Printf(TEXT("window.ue['%s']"), *EscapedName))); + ExecuteJavascript(SetValueScript); + } +} + +void FNativeInterfaceJSScripting::ExecuteJavascript(const FString& Javascript) +{ + TSharedPtr<FNativeWebInterfaceBrowserProxy> Window = WindowPtr.Pin(); + if (Window.IsValid()) + { + Window->ExecuteJavascript(Javascript); + } +} + +void FNativeInterfaceJSScripting::UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + const FString ExposedName = GetBindingName(Name, Object); + if (bIsPermanent) + { + // If overriding an existing permanent object, make it non-permanent + if (PermanentUObjectsByName.Contains(ExposedName) && (Object == nullptr || PermanentUObjectsByName[ExposedName] == Object)) + { + Object = PermanentUObjectsByName.FindAndRemoveChecked(ExposedName); + BoundObjects.Remove(Object); + return; + } + else + { + return; + } + } + + FString DeleteValueScript = FString::Printf(TEXT("delete window.ue['%s'];"), *ExposedName.ReplaceCharWithEscapedChar()); + ExecuteJavascript(DeleteValueScript); +} + +int32 _ParseParams(const FString& ParamStr, TArray<FString>& OutArray) +{ + OutArray.Reset(); + const TCHAR *Start = *ParamStr; + if (Start && *Start != TEXT('\0')) + { + int32 DelimLimit = 4; + while (const TCHAR *At = FCString::Strstr(Start, TEXT("/"))) + { + OutArray.Emplace(At - Start, Start); + Start = At + 1; + if (--DelimLimit == 0) + { + break; + } + } + if (*Start) + { + OutArray.Emplace(Start); + } + } + return OutArray.Num(); +} + +bool FNativeInterfaceJSScripting::OnJsMessageReceived(const FString& Message) +{ + check(IsInGameThread()); + + bool Result = false; + TArray<FString> Params; + if (_ParseParams(Message, Params)) + { + FString Command = Params[0]; + Params.RemoveAt(0, 1); + + if (Command == NativeInterfaceFuncs::ExecuteMethodCommand) + { + Result = HandleExecuteUObjectMethodMessage(Params); + } + } + return Result; +} + +FString FNativeInterfaceJSScripting::ConvertStruct(UStruct* TypeInfo, const void* StructPtr) +{ + TArray<uint8> ReturnBuffer; + FMemoryWriter Writer(ReturnBuffer); + + FNativeInterfaceJSStructSerializerBackend ReturnBackend = FNativeInterfaceJSStructSerializerBackend(SharedThis(this), Writer); + FStructSerializer::Serialize(StructPtr, *TypeInfo, ReturnBackend); + + // Extract the result value from the serialized JSON object: + ReturnBuffer.Add(0); + ReturnBuffer.Add(0); // Add two as we're dealing with UTF-16, so 2 bytes + return UTF16_TO_TCHAR((UTF16CHAR*)ReturnBuffer.GetData()); +} + +FString FNativeInterfaceJSScripting::ConvertObject(UObject* Object) +{ + RetainBinding(Object); + UClass* Class = Object->GetClass(); + + bool first = true; + FString Result = TEXT("(function(){ return Object.create({"); + for (TFieldIterator<UFunction> FunctionIt(Class, EFieldIteratorFlags::IncludeSuper); FunctionIt; ++FunctionIt) + { + UFunction* Function = *FunctionIt; + if(!first) + { + Result.Append(TEXT(",")); + } + else + { + first = false; + } + Result.Append(*GetBindingName(Function)); + Result.Append(TEXT(": function ")); + Result.Append(*GetBindingName(Function)); + Result.Append(TEXT(" (")); + + bool firstArg = true; + for ( TFieldIterator<FProperty> It(Function); It; ++It ) + { + FProperty* Param = *It; + if (Param->PropertyFlags & CPF_Parm && ! (Param->PropertyFlags & CPF_ReturnParm) ) + { + FStructProperty *StructProperty = CastField<FStructProperty>(Param); + if (!StructProperty || !StructProperty->Struct->IsChildOf(FWebInterfaceJSResponse::StaticStruct())) + { + if(!firstArg) + { + Result.Append(TEXT(", ")); + } + else + { + firstArg = false; + } + Result.Append(*GetBindingName(Param)); + } + } + } + + Result.Append(TEXT(")")); + + // We hijack the RPCResponseId and use it for our priority value. 0 means it has not been assigned and we default to 2. 1-5 is high-low priority which we map to the 0-4 range used by EmbeddedCommunication. + int32 Priority = Function->RPCResponseId == 0 ? 2 : FMath::Clamp((int32)Function->RPCResponseId, 1, 5) - 1; + + Result.Append(TEXT(" {return window.ue.$.executeMethod('")); + Result.Append(FString::FromInt(Priority)); + Result.Append(TEXT("',this.$id, arguments)}")); + } + Result.Append(TEXT("},{")); + Result.Append(TEXT("$id: {writable: false, configurable:false, enumerable: false, value: '")); + Result.Append(*PtrToGuid(Object).ToString(EGuidFormats::Digits)); + Result.Append(TEXT("'}})})()")); + return Result; +} + +void FNativeInterfaceJSScripting::InvokeJSFunction(FGuid FunctionId, int32 ArgCount, FWebInterfaceJSParam Arguments[], bool bIsError) +{ + if (!IsValid()) + { + return; + } + + FString CallbackScript = FString::Printf(TEXT("window.ue.$.invokeCallback('%s', %s, "), *FunctionId.ToString(EGuidFormats::Digits), (bIsError) ? TEXT("true") : TEXT("false")); + { + TArray<uint8> Buffer; + FMemoryWriter MemoryWriter(Buffer); + NativeInterfaceFuncs::FJsonWriterRef JsonWriter = TJsonWriter<>::Create(&MemoryWriter); + JsonWriter->WriteArrayStart(); + for (int i = 0; i < ArgCount; i++) + { + NativeInterfaceFuncs::WriteJsParam(SharedThis(this), JsonWriter, i, Arguments[i]); + } + JsonWriter->WriteArrayEnd(); + CallbackScript.Append((TCHAR*)Buffer.GetData(), Buffer.Num() / sizeof(TCHAR)); + } + CallbackScript.Append(TEXT(")")); + + ExecuteJavascript(CallbackScript); + +} + +void FNativeInterfaceJSScripting::InvokeJSFunctionRaw(FGuid FunctionId, const FString& RawJSValue, bool bIsError) +{ + if (!IsValid()) + { + return; + } + + FString CallbackScript = FString::Printf(TEXT("window.ue.$.invokeCallback('%s', %s, [%s])"), + *FunctionId.ToString(EGuidFormats::Digits), (bIsError)?TEXT("true"):TEXT("false"), *RawJSValue); + ExecuteJavascript(CallbackScript); +} + +void FNativeInterfaceJSScripting::InvokeJSErrorResult(FGuid FunctionId, const FString& Error) +{ + FWebInterfaceJSParam Args[1] = {FWebInterfaceJSParam(Error)}; + InvokeJSFunction(FunctionId, 1, Args, true); +} + +bool FNativeInterfaceJSScripting::HandleExecuteUObjectMethodMessage(const TArray<FString>& MessageArgs) +{ + if (MessageArgs.Num() != 4) + { + return false; + } + + const FString& ObjectIdStr = MessageArgs[0]; + FGuid ObjectKey; + UObject* Object = nullptr; + if (FGuid::Parse(ObjectIdStr, ObjectKey)) + { + Object = GuidToPtr(ObjectKey); + } + else if(PermanentUObjectsByName.Contains(ObjectIdStr)) + { + Object = PermanentUObjectsByName[ObjectIdStr]; + } + + if(Object == nullptr) + { + // Unknown uobject id/name + return false; + } + + // Get the promise callback and use that to report any results from executing this function. + FGuid ResultCallbackId; + if (!FGuid::Parse(MessageArgs[1], ResultCallbackId)) + { + // Invalid GUID + return false; + } + + FName MethodName = FName(*MessageArgs[2]); + UFunction* Function = Object->FindFunction(MethodName); + if (!Function) + { + InvokeJSErrorResult(ResultCallbackId, TEXT("Unknown UObject Function")); + return true; + } + + // Coerce arguments to function arguments. + uint16 ParamsSize = Function->ParmsSize; + TArray<uint8> Params; + FProperty* ReturnParam = nullptr; + FProperty* PromiseParam = nullptr; + + if (ParamsSize > 0) + { + // Find return parameter and a promise argument if present, as we need to handle them differently + for ( TFieldIterator<FProperty> It(Function); It; ++It ) + { + FProperty* Param = *It; + if (Param->PropertyFlags & CPF_Parm) + { + if (Param->PropertyFlags & CPF_ReturnParm) + { + ReturnParam = Param; + } + else + { + FStructProperty *StructProperty = CastField<FStructProperty>(Param); + if (StructProperty && StructProperty->Struct->IsChildOf(FWebInterfaceJSResponse::StaticStruct())) + { + PromiseParam = Param; + } + } + if (ReturnParam && PromiseParam) + { + break; + } + } + } + + // UFunction is a subclass of UStruct, so we can treat the arguments as a struct for deserialization + Params.AddUninitialized(ParamsSize); + Function->InitializeStruct(Params.GetData()); + + // Note: This is a no-op on platforms that are using a 16-bit TCHAR + FTCHARToUTF16 UTF16String(*MessageArgs[3], MessageArgs[3].Len()); + + TArray<uint8> JsonData; + JsonData.Append((uint8*)UTF16String.Get(), UTF16String.Length() * sizeof(UTF16CHAR)); + + FMemoryReader Reader(JsonData); + FNativeInterfaceJSStructDeserializerBackend Backend = FNativeInterfaceJSStructDeserializerBackend(SharedThis(this), Reader); + FStructDeserializer::Deserialize(Params.GetData(), *Function, Backend); + } + + if (PromiseParam) + { + FWebInterfaceJSResponse* PromisePtr = PromiseParam->ContainerPtrToValuePtr<FWebInterfaceJSResponse>(Params.GetData()); + if (PromisePtr) + { + *PromisePtr = FWebInterfaceJSResponse(SharedThis(this), ResultCallbackId); + } + } + + Object->ProcessEvent(Function, Params.GetData()); + if ( ! PromiseParam ) // If PromiseParam is set, we assume that the UFunction will ensure it is called with the result + { + if ( ReturnParam ) + { + FStructSerializerPolicies ReturnPolicies; + ReturnPolicies.PropertyFilter = [&ReturnParam](const FProperty* CandidateProperty, const FProperty* ParentProperty) + { + return ParentProperty != nullptr || CandidateProperty == ReturnParam; + }; + TArray<uint8> ReturnBuffer; + FMemoryWriter Writer(ReturnBuffer); + + FNativeInterfaceJSStructSerializerBackend ReturnBackend = FNativeInterfaceJSStructSerializerBackend(SharedThis(this), Writer); + FStructSerializer::Serialize(Params.GetData(), *Function, ReturnBackend, ReturnPolicies); + + // Extract the result value from the serialized JSON object: + ReturnBuffer.Add(0); + ReturnBuffer.Add(0); // Add two as we're dealing with UTF-16, so 2 bytes + const FString ResultJS = UTF16_TO_TCHAR((UTF16CHAR*)ReturnBuffer.GetData()); + + InvokeJSFunctionRaw(ResultCallbackId, ResultJS, false); + } + else + { + InvokeJSFunction(ResultCallbackId, 0, nullptr, false); + } + } + return true; +} + +FString FNativeInterfaceJSScripting::GetInitializeScript() +{ + const FString NativeScriptingInit = + TEXT("(function() {") + TEXT("var util = Object.create({") + + // Simple random-based (RFC-4122 version 4) UUID generator. + // Version 4 UUIDs have the form xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx where x is any hexadecimal digit and y is one of 8, 9, a, or b + // This function returns the UUID as a hex string without the dashes + TEXT("uuid: function()") + TEXT("{") + TEXT(" var b = new Uint8Array(16); window.crypto.getRandomValues(b);") + TEXT(" b[6] = b[6]&0xf|0x40; b[8]=b[8]&0x3f|0x80;") // Set the reserved bits to the correct values + TEXT(" return Array.prototype.reduce.call(b, function(a,i){return a+((0x100|i).toString(16).substring(1))},'').toUpperCase();") + TEXT("}, ") + + // save a callback function in the callback registry + // returns the uuid of the callback for passing to the host application + // ensures that each function object is only stored once. + // (Closures executed multiple times are considered separate objects.) + TEXT("registerCallback: function(callback)") + TEXT("{") + TEXT(" var key;") + TEXT(" for(key in this.callbacks)") + TEXT(" {") + TEXT(" if (!this.callbacks[key].isOneShot && this.callbacks[key].accept === callback)") + TEXT(" {") + TEXT(" return key;") + TEXT(" }") + TEXT(" }") + TEXT(" key = this.uuid();") + TEXT(" this.callbacks[key] = {accept:callback, reject:callback, bIsOneShot:false};") + TEXT(" return key;") + TEXT("}, ") + + TEXT("registerPromise: function(accept, reject, name)") + TEXT("{") + TEXT(" var key = this.uuid();") + TEXT(" this.callbacks[key] = {accept:accept, reject:reject, bIsOneShot:true, name:name};") + TEXT(" return key;") + TEXT("}, ") + + // strip ReturnValue object wrapper if present + TEXT("returnValToObj: function(args)") + TEXT("{") + TEXT(" return Array.prototype.map.call(args, function(item){return item.ReturnValue || item});") + TEXT("}, ") + + // invoke a callback method or promise by uuid + TEXT("invokeCallback: function(key, bIsError, args)") + TEXT("{") + TEXT(" var callback = this.callbacks[key];") + TEXT(" if (typeof callback === 'undefined')") + TEXT(" {") + TEXT(" console.error('Unknown callback id', key);") + TEXT(" return;") + TEXT(" }") + TEXT(" if (callback.bIsOneShot)") + TEXT(" {") + TEXT(" callback.iwanttodeletethis=true;") + TEXT(" delete this.callbacks[key];") + TEXT(" }") + TEXT(" callback[bIsError?'reject':'accept'].apply(window, this.returnValToObj(args));") + TEXT("}, ") + + // convert an argument list to a dictionary of arguments. + // The args argument must be an argument object as it uses the callee member to deduce the argument names + TEXT("argsToDict: function(args)") + TEXT("{") + TEXT(" var res = {};") + TEXT(" args.callee.toString().match(/\\((.+?)\\)/)[1].split(/\\s*,\\s*/).forEach(function(name, idx){res[name]=args[idx]});") + TEXT(" return res;") + TEXT("}, ") + + // encodes and sends a message to the host application + TEXT("sendMessage: function()") + TEXT("{") + // @todo: Each kairos native browser will have a different way of passing a message out, here we use webkit postmessage but we'll need + // to be aware of our target platform when generating this script and adjust accordingly + TEXT(" var delimiter = '/';") + +#if PLATFORM_ANDROID + TEXT(" if(window.JSBridge){") + TEXT(" window.JSBridge.postMessage('', 'browserProxy', 'handlejs', Array.prototype.slice.call(arguments).join(delimiter));") + TEXT(" }") +#else + TEXT(" if(window.webkit && window.webkit.messageHandlers && window.webkit.messageHandlers.browserProxy){") + TEXT(" window.webkit.messageHandlers.browserProxy.postMessage(Array.prototype.slice.call(arguments).join(delimiter));") + TEXT(" }") +#endif + TEXT("}, ") + + // custom replacer function passed into JSON.stringify to handle cases where there are function objects in the argument list + // of the executeMethod call. In those cases we want to be able to pass them as callbacks. + TEXT("customReplacer: function(key, value)") + TEXT("{") + TEXT(" if (typeof value === 'function')") + TEXT(" {") + TEXT(" return window.ue.$.registerCallback(value);") + TEXT(" }") + TEXT(" return value;") + TEXT("},") + + // uses the above helper methods to execute a method on a uobject instance. + // the method set as callee on args needs to be a named function, as the name of the method to invoke is taken from it + TEXT("executeMethod: function(priority, id, args)") + TEXT("{") + TEXT(" var self = this;") // the closures need access to the outer this object + + // Create a promise object to return back to the caller and create a callback function to handle the response + TEXT(" var promiseID;") + TEXT(" var promise = new Promise(function (accept, reject) ") + TEXT(" {") + TEXT(" promiseID = self.registerPromise(accept, reject, args.callee.name)") + TEXT(" });") + + // Actually invoke the method by sending a message to the host app + TEXT(" this.sendMessage(priority, '") + NativeInterfaceFuncs::ExecuteMethodCommand + TEXT("', id, promiseID, args.callee.name, JSON.stringify(this.argsToDict(args), this.customReplacer));") + + // Return the promise object to the caller + TEXT(" return promise;") + TEXT("}") + TEXT("},{callbacks: {value:{}}});") + + // Create the global window.ue variable + TEXT("window.ue = Object.create({}, {'$': {writable: false, configurable:false, enumerable: false, value:util}});") + TEXT("})();") + ; + + return NativeScriptingInit; +} + +void FNativeInterfaceJSScripting::PageLoaded() +{ + // Expunge temporary objects. + for (TMap<UObject*, ObjectBinding>::TIterator It(BoundObjects); It; ++It) + { + if (!It->Value.bIsPermanent) + { + It.RemoveCurrent(); + } + } + + FString Script = GetInitializeScript(); + + for(auto& Item : PermanentUObjectsByName) + { + Script.Append(*FString::Printf(TEXT("window.ue['%s'] = %s;"), *Item.Key.ReplaceCharWithEscapedChar(), *ConvertObject(Item.Value))); + } + + // Append postinit for each object we added. + for (auto& Item : PermanentUObjectsByName) + { + const FString& Name = Item.Key.ReplaceCharWithEscapedChar(); + Script.Append(_GetObjectPostInitScript(Name, FString::Printf(TEXT("window.ue['%s']"), *Name))); + } + + // Append postinit for window.ue + Script.Append(_GetObjectPostInitScript(TEXT("ue"), TEXT("window.ue"))); + + bLoaded = true; + ExecuteJavascript(Script); +} + +FNativeInterfaceJSScripting::FNativeInterfaceJSScripting(bool bJSBindingToLoweringEnabled, TSharedRef<FNativeWebInterfaceBrowserProxy> Window) + : FWebInterfaceJSScripting(bJSBindingToLoweringEnabled) + , bLoaded(false) +{ + WindowPtr = Window; +} + diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSScripting.h b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSScripting.h new file mode 100644 index 0000000000000000000000000000000000000000..e46192a3d8e9eb9584b4fec924e41134e9b61ffe --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSScripting.h @@ -0,0 +1,52 @@ +// Engine/Source/Runtime/WebBrowser/Private/Native/NativeJSScripting.h +#pragma once + +#include "CoreMinimal.h" + +#include "WebInterfaceJSFunction.h" +#include "WebInterfaceJSScripting.h" + +typedef TSharedRef<class FNativeInterfaceJSScripting> FNativeInterfaceJSScriptingRef; +typedef TSharedPtr<class FNativeInterfaceJSScripting> FNativeInterfaceJSScriptingPtr; + +class FNativeWebInterfaceBrowserProxy; + +/** + * Implements handling of bridging UObjects client side with JavaScript renderer side. + */ +class FNativeInterfaceJSScripting + : public FWebInterfaceJSScripting + , public TSharedFromThis<FNativeInterfaceJSScripting> +{ +public: + //static const FString JSMessageTag; + + FNativeInterfaceJSScripting(bool bJSBindingToLoweringEnabled, TSharedRef<FNativeWebInterfaceBrowserProxy> Window); + + virtual void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) override; + virtual void UnbindUObject(const FString& Name, UObject* Object = nullptr, bool bIsPermanent = true) override; + + bool OnJsMessageReceived(const FString& Message); + + FString ConvertStruct(UStruct* TypeInfo, const void* StructPtr); + FString ConvertObject(UObject* Object); + + virtual void InvokeJSFunction(FGuid FunctionId, int32 ArgCount, FWebInterfaceJSParam Arguments[], bool bIsError=false) override; + virtual void InvokeJSErrorResult(FGuid FunctionId, const FString& Error) override; + void PageLoaded(); + +private: + FString GetInitializeScript(); + void InvokeJSFunctionRaw(FGuid FunctionId, const FString& JSValue, bool bIsError=false); + bool IsValid() + { + return WindowPtr.Pin().IsValid(); + } + + /** Message handling helpers */ + bool HandleExecuteUObjectMethodMessage(const TArray<FString>& Params); + void ExecuteJavascript(const FString& Javascript); + + TWeakPtr<FNativeWebInterfaceBrowserProxy> WindowPtr; + bool bLoaded; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructDeserializerBackend.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructDeserializerBackend.cpp new file mode 100644 index 0000000000000000000000000000000000000000..418a82db125181d87b720d92c7d775e8abbd5dd3 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructDeserializerBackend.cpp @@ -0,0 +1,98 @@ +// Engine/Source/Runtime/WebBrowser/Private/Native/NativeJSStructDeserializerBackend.cpp + +#include "NativeInterfaceJSStructDeserializerBackend.h" +#include "NativeInterfaceJSScripting.h" +#include "UObject/UnrealType.h" +#include "Templates/Casts.h" + +namespace NativeInterfaceFuncs +{ + // @todo: this function is copied from CEFJSStructDeserializerBackend.cpp. Move shared utility code to a common header file + /** + * Sets the value of the given property. + * + * @param Property The property to set. + * @param Outer The property that contains the property to be set, if any. + * @param Data A pointer to the memory holding the property's data. + * @param ArrayIndex The index of the element to set (if the property is an array). + * @return true on success, false otherwise. + * @see ClearPropertyValue + */ + template<typename FPropertyType, typename PropertyType> + bool SetPropertyValue( FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex, const PropertyType& Value ) + { + PropertyType* ValuePtr = nullptr; + FArrayProperty* ArrayProperty = CastField<FArrayProperty>(Outer); + + if (ArrayProperty != nullptr) + { + if (ArrayProperty->Inner != Property) + { + return false; + } + + FScriptArrayHelper ArrayHelper(ArrayProperty, ArrayProperty->template ContainerPtrToValuePtr<void>(Data)); + int32 Index = ArrayHelper.AddValue(); + + ValuePtr = (PropertyType*)ArrayHelper.GetRawPtr(Index); + } + else + { + FPropertyType* TypedProperty = CastField<FPropertyType>(Property); + + if (TypedProperty == nullptr || ArrayIndex >= TypedProperty->ArrayDim) + { + return false; + } + + ValuePtr = TypedProperty->template ContainerPtrToValuePtr<PropertyType>(Data, ArrayIndex); + } + + if (ValuePtr == nullptr) + { + return false; + } + + *ValuePtr = Value; + + return true; + } +} + + +bool FNativeInterfaceJSStructDeserializerBackend::ReadProperty( FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex ) +{ + switch (GetLastNotation()) + { + case EJsonNotation::String: + { + if (Property->IsA<FStructProperty>()) + { + FStructProperty* StructProperty = CastField<FStructProperty>(Property); + + if ( StructProperty->Struct == FWebInterfaceJSFunction::StaticStruct()) + { + + FGuid CallbackID; + if (!FGuid::Parse(GetReader()->GetValueAsString(), CallbackID)) + { + return false; + } + + FWebInterfaceJSFunction CallbackObject(Scripting, CallbackID); + return NativeInterfaceFuncs::SetPropertyValue<FStructProperty, FWebInterfaceJSFunction>(Property, Outer, Data, ArrayIndex, CallbackObject); + } + } + } + break; + } + + // If we reach this, default to parent class behavior + return FJsonStructDeserializerBackend::ReadProperty(Property, Outer, Data, ArrayIndex); +} + +FNativeInterfaceJSStructDeserializerBackend::FNativeInterfaceJSStructDeserializerBackend(FNativeInterfaceJSScriptingRef InScripting, FMemoryReader& Reader) + : FJsonStructDeserializerBackend(Reader) + , Scripting(InScripting) +{ +} diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructDeserializerBackend.h b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructDeserializerBackend.h new file mode 100644 index 0000000000000000000000000000000000000000..81be20bdba349a7f73bd07e951928c1b6c6f979c --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructDeserializerBackend.h @@ -0,0 +1,21 @@ +// Engine/Source/Runtime/WebBrowser/Private/Native/NativeJSStructDeserializerBackend.h + +#pragma once + +#include "CoreMinimal.h" +#include "NativeInterfaceJSScripting.h" +#include "Backends/JsonStructDeserializerBackend.h" +#include "Serialization/MemoryReader.h" + +class FNativeInterfaceJSStructDeserializerBackend + : public FJsonStructDeserializerBackend +{ +public: + FNativeInterfaceJSStructDeserializerBackend(FNativeInterfaceJSScriptingRef InScripting, FMemoryReader& Reader); + + virtual bool ReadProperty( FProperty* Property, FProperty* Outer, void* Data, int32 ArrayIndex ) override; + +private: + FNativeInterfaceJSScriptingRef Scripting; + +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructSerializerBackend.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructSerializerBackend.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e0965da3834cfc6cf4333472b0f2eab5da8ed859 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructSerializerBackend.cpp @@ -0,0 +1,48 @@ +// Engine/Source/Runtime/WebBrowser/Private/Native/NativeJSStructSerializerBackend.cpp + +#include "Native/NativeInterfaceJSStructSerializerBackend.h" + +#include "NativeInterfaceJSScripting.h" +#include "UObject/UnrealType.h" +#include "UObject/PropertyPortFlags.h" +#include "Templates/Casts.h" + +void FNativeInterfaceJSStructSerializerBackend::WriteProperty(const FStructSerializerState& State, int32 ArrayIndex) +{ + // The parent class serialzes UObjects as NULLs + if (FObjectProperty* ObjectProperty = CastField<FObjectProperty>(State.ValueProperty)) + { + WriteUObject(State, CastFieldChecked<FObjectProperty>(State.ValueProperty)->GetPropertyValue_InContainer(State.ValueData, ArrayIndex)); + } + // basic property type (json serializable) + else + { + FJsonStructSerializerBackend::WriteProperty(State, ArrayIndex); + } +} + +void FNativeInterfaceJSStructSerializerBackend::WriteUObject(const FStructSerializerState& State, UObject* Value) +{ + // Note this function uses WriteRawJSONValue to append non-json data to the output stream. + FString RawValue = Scripting->ConvertObject(Value); + if ((State.ValueProperty == nullptr) || (State.ValueProperty->ArrayDim > 1) || State.ValueProperty->GetOwner< FArrayProperty>()) + { + GetWriter()->WriteRawJSONValue(RawValue); + } + else if (State.KeyProperty != nullptr) + { + FString KeyString; + State.KeyProperty->ExportTextItem_Direct(KeyString, State.KeyData, nullptr, nullptr, PPF_None); + GetWriter()->WriteRawJSONValue(KeyString, RawValue); + } + else + { + GetWriter()->WriteRawJSONValue(Scripting->GetBindingName(State.ValueProperty), RawValue); + } +} + +FNativeInterfaceJSStructSerializerBackend::FNativeInterfaceJSStructSerializerBackend(TSharedRef<class FNativeInterfaceJSScripting> InScripting, FMemoryWriter& Writer) + : FJsonStructSerializerBackend(Writer, EStructSerializerBackendFlags::Default) + , Scripting(InScripting) +{ +} \ No newline at end of file diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructSerializerBackend.h b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructSerializerBackend.h new file mode 100644 index 0000000000000000000000000000000000000000..1404e1fb906aa37242fdeff66f91ed69e3872204 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeInterfaceJSStructSerializerBackend.h @@ -0,0 +1,36 @@ +// Engine/Source/Runtime/WebBrowser/Private/Native/NativeJSStructSerializerBackend.h + +#pragma once + +#include "CoreMinimal.h" +#include "NativeInterfaceJSScripting.h" +#include "Backends/JsonStructSerializerBackend.h" + +class UObject; + +/** + * Implements a writer for UStruct serialization using JavaScript. + * + * Based on FJsonStructSerializerBackend, it adds support for certain object types not representable in pure JSON + * + */ +class FNativeInterfaceJSStructSerializerBackend + : public FJsonStructSerializerBackend +{ +public: + + /** + * Creates and initializes a new instance. + * + * @param InScripting An instance of a web browser scripting obnject. + */ + FNativeInterfaceJSStructSerializerBackend(FNativeInterfaceJSScriptingRef InScripting, FMemoryWriter& Writer); + +public: + virtual void WriteProperty(const FStructSerializerState& State, int32 ArrayIndex = 0) override; + +private: + void WriteUObject(const FStructSerializerState& State, UObject* Value); + + FNativeInterfaceJSScriptingRef Scripting; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeWebInterfaceBrowserProxy.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeWebInterfaceBrowserProxy.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2e4cea57b2b608aa284ffb78255a86ef665b6ca1 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeWebInterfaceBrowserProxy.cpp @@ -0,0 +1,243 @@ +// Engine/Source/Runtime/WebBrowser/Private/Native/NativeWebBrowserProxy.h + + +#include "NativeWebInterfaceBrowserProxy.h" +#include "NativeInterfaceJSScripting.h" +#include "Misc/EmbeddedCommunication.h" + + +FNativeWebInterfaceBrowserProxy::FNativeWebInterfaceBrowserProxy(bool bInJSBindingToLoweringEnabled) + : bJSBindingToLoweringEnabled(bInJSBindingToLoweringEnabled) +{ + +} + +void FNativeWebInterfaceBrowserProxy::Initialize() +{ + Scripting = MakeShareable(new FNativeInterfaceJSScripting(bJSBindingToLoweringEnabled, SharedThis(this))); + FEmbeddedDelegates::GetNativeToEmbeddedParamsDelegateForSubsystem(TEXT("browserProxy")).AddRaw(this, &FNativeWebInterfaceBrowserProxy::HandleEmbeddedCommunication); +} + +FNativeWebInterfaceBrowserProxy::~FNativeWebInterfaceBrowserProxy() +{ + FEmbeddedDelegates::GetNativeToEmbeddedParamsDelegateForSubsystem(TEXT("browserProxy")).RemoveAll(this); +} + +bool FNativeWebInterfaceBrowserProxy::OnJsMessageReceived(const FString& Message) +{ + return Scripting->OnJsMessageReceived(Message); +} + +void FNativeWebInterfaceBrowserProxy::HandleEmbeddedCommunication(const FEmbeddedCallParamsHelper& Params) +{ + FString Error; + if (Params.Command == "handlejs") + { + FString Message = Params.Parameters.FindRef(TEXT("script")); + if (!Message.IsEmpty()) + { + if (!OnJsMessageReceived(Message)) + { + Error = TEXT("Command failed"); + } + } + } + else if (Params.Command == "pageload") + { + Scripting->PageLoaded(); + } + + Params.OnCompleteDelegate(FEmbeddedCommunicationMap(), Error); +} + +void FNativeWebInterfaceBrowserProxy::LoadURL(FString NewURL) +{ +} + +void FNativeWebInterfaceBrowserProxy::LoadString(FString Contents, FString DummyURL) +{ +} + +void FNativeWebInterfaceBrowserProxy::SetViewportSize(FIntPoint WindowSize, FIntPoint WindowPos) +{ +} + +FIntPoint FNativeWebInterfaceBrowserProxy::GetViewportSize() const +{ + return FIntPoint(ForceInitToZero); +} + +FSlateShaderResource* FNativeWebInterfaceBrowserProxy::GetTexture(bool bIsPopup /*= false*/) +{ + return nullptr; +} + +bool FNativeWebInterfaceBrowserProxy::IsValid() const +{ + return false; +} + +bool FNativeWebInterfaceBrowserProxy::IsInitialized() const +{ + return true; +} + +bool FNativeWebInterfaceBrowserProxy::IsClosing() const +{ + return false; +} + +EWebInterfaceBrowserDocumentState FNativeWebInterfaceBrowserProxy::GetDocumentLoadingState() const +{ + return EWebInterfaceBrowserDocumentState::Loading; +} + +FString FNativeWebInterfaceBrowserProxy::GetTitle() const +{ + return TEXT(""); +} + +FString FNativeWebInterfaceBrowserProxy::GetUrl() const +{ + return TEXT(""); +} + +void FNativeWebInterfaceBrowserProxy::GetSource(TFunction<void(const FString&)> Callback) const +{ + Callback(FString()); +} + +void FNativeWebInterfaceBrowserProxy::SetSupportsMouseWheel(bool bValue) +{ + +} + +bool FNativeWebInterfaceBrowserProxy::GetSupportsMouseWheel() const +{ + return false; +} + +bool FNativeWebInterfaceBrowserProxy::OnKeyDown(const FKeyEvent& InKeyEvent) +{ + return false; +} + +bool FNativeWebInterfaceBrowserProxy::OnKeyUp(const FKeyEvent& InKeyEvent) +{ + return false; +} + +bool FNativeWebInterfaceBrowserProxy::OnKeyChar(const FCharacterEvent& InCharacterEvent) +{ + return false; +} + +FReply FNativeWebInterfaceBrowserProxy::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + return FReply::Unhandled(); +} + +FReply FNativeWebInterfaceBrowserProxy::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + return FReply::Unhandled(); +} + +FReply FNativeWebInterfaceBrowserProxy::OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + return FReply::Unhandled(); +} + +FReply FNativeWebInterfaceBrowserProxy::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + return FReply::Unhandled(); +} + +void FNativeWebInterfaceBrowserProxy::OnMouseLeave(const FPointerEvent& MouseEvent) +{ +} + +FReply FNativeWebInterfaceBrowserProxy::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) +{ + return FReply::Unhandled(); +} + + +void FNativeWebInterfaceBrowserProxy::OnFocus(bool SetFocus, bool bIsPopup) +{ +} + +void FNativeWebInterfaceBrowserProxy::OnCaptureLost() +{ +} + +bool FNativeWebInterfaceBrowserProxy::CanGoBack() const +{ + return false; +} + +void FNativeWebInterfaceBrowserProxy::GoBack() +{ +} + +bool FNativeWebInterfaceBrowserProxy::CanGoForward() const +{ + return false; +} + +void FNativeWebInterfaceBrowserProxy::GoForward() +{ +} + +bool FNativeWebInterfaceBrowserProxy::IsLoading() const +{ + return false; +} + +void FNativeWebInterfaceBrowserProxy::Reload() +{ +} + +void FNativeWebInterfaceBrowserProxy::StopLoad() +{ +} + +void FNativeWebInterfaceBrowserProxy::ExecuteJavascript(const FString& Script) +{ + FEmbeddedCallParamsHelper CallHelper; + CallHelper.Command = TEXT("execjs"); + CallHelper.Parameters = { { TEXT("script"), Script } }; + + FEmbeddedDelegates::GetEmbeddedToNativeParamsDelegateForSubsystem(TEXT("webview")).Broadcast(CallHelper); +} + +void FNativeWebInterfaceBrowserProxy::CloseBrowser(bool bForce, bool bBlockTillClosed /* ignored */) +{ +} + +void FNativeWebInterfaceBrowserProxy::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent /*= true*/) +{ + Scripting->BindUObject(Name, Object, bIsPermanent); +} + +void FNativeWebInterfaceBrowserProxy::UnbindUObject(const FString& Name, UObject* Object /*= nullptr*/, bool bIsPermanent /*= true*/) +{ + Scripting->UnbindUObject(Name, Object, bIsPermanent); +} + +int FNativeWebInterfaceBrowserProxy::GetLoadError() +{ + return 0; +} + +void FNativeWebInterfaceBrowserProxy::SetIsDisabled(bool bValue) +{ +} + +TSharedPtr<SWindow> FNativeWebInterfaceBrowserProxy::GetParentWindow() const +{ + return nullptr; +} + +void FNativeWebInterfaceBrowserProxy::SetParentWindow(TSharedPtr<SWindow> Window) +{ +} diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeWebInterfaceBrowserProxy.h b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeWebInterfaceBrowserProxy.h new file mode 100644 index 0000000000000000000000000000000000000000..422ae4050a8c00ed44ade3ec0672e406d79d68f2 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/Native/NativeWebInterfaceBrowserProxy.h @@ -0,0 +1,261 @@ +// Engine/Source/Runtime/WebBrowser/Private/Native/NativeWebBrowserProxy.h +#pragma once + +#include "CoreMinimal.h" +#include "IWebInterfaceBrowserWindow.h" +#include "NativeInterfaceJSScripting.h" + +class FNativeWebInterfaceBrowserProxy + : public IWebInterfaceBrowserWindow + , public TSharedFromThis<FNativeWebInterfaceBrowserProxy> +{ + // For creating instances of this class + friend class FWebInterfaceBrowserSingleton; + +private: + FNativeWebInterfaceBrowserProxy(bool bJSBindingToLoweringEnabled); + void Initialize(); + void HandleEmbeddedCommunication(const struct FEmbeddedCallParamsHelper& Params); + bool OnJsMessageReceived(const FString& Message); + +public: + virtual ~FNativeWebInterfaceBrowserProxy(); + +public: + // IWebBrowserWindow Interface + + virtual void LoadURL(FString NewURL) override; + virtual void LoadString(FString Contents, FString DummyURL) override; + virtual void SetViewportSize(FIntPoint WindowSize, FIntPoint WindowPos) override; + virtual FIntPoint GetViewportSize() const override; + + virtual class FSlateShaderResource* GetTexture(bool bIsPopup = false) override; + virtual bool IsValid() const override; + virtual bool IsInitialized() const override; + virtual bool IsClosing() const override; + virtual EWebInterfaceBrowserDocumentState GetDocumentLoadingState() const override; + virtual FString GetTitle() const override; + virtual FString GetUrl() const override; + virtual void GetSource(TFunction<void(const FString&)> Callback) const override; + virtual bool OnKeyDown(const FKeyEvent& InKeyEvent) override; + virtual bool OnKeyUp(const FKeyEvent& InKeyEvent) override; + virtual bool OnKeyChar(const FCharacterEvent& InCharacterEvent) override; + virtual void SetSupportsMouseWheel(bool bValue) override; + virtual bool GetSupportsMouseWheel() const override; + + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual FReply OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) override; + virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) override; + virtual FOnDragWindow& OnDragWindow() override + { + return DragWindowDelegate; + } + virtual void OnFocus(bool SetFocus, bool bIsPopup) override; + virtual void OnCaptureLost() override; + virtual bool CanGoBack() const override; + virtual void GoBack() override; + virtual bool CanGoForward() const override; + virtual void GoForward() override; + virtual bool IsLoading() const override; + virtual void Reload() override; + virtual void StopLoad() override; + virtual void ExecuteJavascript(const FString& Script) override; + virtual void CloseBrowser(bool bForce, bool bBlockTillClosed) override; + virtual void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) override; + virtual void UnbindUObject(const FString& Name, UObject* Object = nullptr, bool bIsPermanent = true) override; + virtual int GetLoadError() override; + virtual void SetIsDisabled(bool bValue) override; + virtual TSharedPtr<SWindow> GetParentWindow() const override; + virtual void SetParentWindow(TSharedPtr<SWindow> Window) override; + + // @todo: None of these are actually called at the moment. + DECLARE_DERIVED_EVENT(FNativeWebInterfaceBrowserProxy, IWebInterfaceBrowserWindow::FOnDocumentStateChanged, FOnDocumentStateChanged); + virtual FOnDocumentStateChanged& OnDocumentStateChanged() override + { + return DocumentStateChangedEvent; + } + + DECLARE_DERIVED_EVENT(FNativeWebInterfaceBrowserProxy, IWebInterfaceBrowserWindow::FOnTitleChanged, FOnTitleChanged); + virtual FOnTitleChanged& OnTitleChanged() override + { + return TitleChangedEvent; + } + + DECLARE_DERIVED_EVENT(FNativeWebInterfaceBrowserProxy, IWebInterfaceBrowserWindow::FOnUrlChanged, FOnUrlChanged); + virtual FOnUrlChanged& OnUrlChanged() override + { + return UrlChangedEvent; + } + + DECLARE_DERIVED_EVENT(FNativeWebInterfaceBrowserProxy, IWebInterfaceBrowserWindow::FOnToolTip, FOnToolTip); + virtual FOnToolTip& OnToolTip() override + { + return ToolTipEvent; + } + + DECLARE_DERIVED_EVENT(FNativeWebInterfaceBrowserProxy, IWebInterfaceBrowserWindow::FOnNeedsRedraw, FOnNeedsRedraw); + virtual FOnNeedsRedraw& OnNeedsRedraw() override + { + return NeedsRedrawEvent; + } + + virtual FOnBeforeBrowse& OnBeforeBrowse() override + { + return BeforeBrowseDelegate; + } + + virtual FOnLoadUrl& OnLoadUrl() override + { + return LoadUrlDelegate; + } + + virtual FOnCreateWindow& OnCreateWindow() override + { + return CreateWindowDelegate; + } + + virtual FOnCloseWindow& OnCloseWindow() override + { + return CloseWindowDelegate; + } + + virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) override + { + return FCursorReply::Unhandled(); + } + + virtual FOnBeforePopupDelegate& OnBeforePopup() override + { + return BeforePopupDelegate; + } + + virtual FOnBeforeResourceLoadDelegate& OnBeforeResourceLoad() override + { + return BeforeResourceLoadDelegate; + } + + virtual FOnResourceLoadCompleteDelegate& OnResourceLoadComplete() override + { + return ResourceLoadCompleteDelegate; + } + + virtual FOnConsoleMessageDelegate& OnConsoleMessage() override + { + return ConsoleMessageDelegate; + } + + DECLARE_DERIVED_EVENT(FNativeWebInterfaceBrowserProxy, IWebInterfaceBrowserWindow::FOnShowPopup, FOnShowPopup); + virtual FOnShowPopup& OnShowPopup() override + { + return ShowPopupEvent; + } + + DECLARE_DERIVED_EVENT(FNativeWebInterfaceBrowserProxy, IWebInterfaceBrowserWindow::FOnDismissPopup, FOnDismissPopup); + virtual FOnDismissPopup& OnDismissPopup() override + { + return DismissPopupEvent; + } + + virtual FOnShowDialog& OnShowDialog() override + { + return ShowDialogDelegate; + } + + virtual FOnDismissAllDialogs& OnDismissAllDialogs() override + { + return DismissAllDialogsDelegate; + } + + virtual FOnSuppressContextMenu& OnSuppressContextMenu() override + { + return SuppressContextMenuDelgate; + } + + virtual FOnUnhandledKeyDown& OnUnhandledKeyDown() override + { + return UnhandledKeyDownDelegate; + } + + virtual FOnUnhandledKeyUp& OnUnhandledKeyUp() override + { + return UnhandledKeyUpDelegate; + } + + virtual FOnUnhandledKeyChar& OnUnhandledKeyChar() override + { + return UnhandledKeyCharDelegate; + } + +private: + + /** Delegate for broadcasting load state changes. */ + FOnDocumentStateChanged DocumentStateChangedEvent; + + /** Delegate for broadcasting title changes. */ + FOnTitleChanged TitleChangedEvent; + + /** Delegate for broadcasting address changes. */ + FOnUrlChanged UrlChangedEvent; + + /** Delegate for broadcasting when the browser wants to show a tool tip. */ + FOnToolTip ToolTipEvent; + + /** Delegate for notifying that the window needs refreshing. */ + FOnNeedsRedraw NeedsRedrawEvent; + + /** Delegate that is executed prior to browser navigation. */ + FOnBeforeBrowse BeforeBrowseDelegate; + + /** Delegate for overriding Url contents. */ + FOnLoadUrl LoadUrlDelegate; + + /** Delegate for notifying that a popup window is attempting to open. */ + FOnBeforePopupDelegate BeforePopupDelegate; + + /** Delegate that is invoked before the browser loads a resource. Its primary purpose is to inject headers into the request. */ + FOnBeforeResourceLoadDelegate BeforeResourceLoadDelegate; + + /** Delegate that is invoked on completion of browser resource loads. Its primary purpose is to allow response to failures. */ + FOnResourceLoadCompleteDelegate ResourceLoadCompleteDelegate; + + /** Delegate that is invoked for each console message */ + FOnConsoleMessageDelegate ConsoleMessageDelegate; + + /** Delegate for handling requests to create new windows. */ + FOnCreateWindow CreateWindowDelegate; + + /** Delegate for handling requests to close new windows that were created. */ + FOnCloseWindow CloseWindowDelegate; + + /** Delegate for handling requests to show the popup menu. */ + FOnShowPopup ShowPopupEvent; + + /** Delegate for handling requests to dismiss the current popup menu. */ + FOnDismissPopup DismissPopupEvent; + + /** Delegate for showing dialogs. */ + FOnShowDialog ShowDialogDelegate; + + /** Delegate for dismissing all dialogs. */ + FOnDismissAllDialogs DismissAllDialogsDelegate; + + /** Delegate for suppressing context menu */ + FOnSuppressContextMenu SuppressContextMenuDelgate; + + /** Delegate for handling key down events not handled by the browser. */ + FOnUnhandledKeyDown UnhandledKeyDownDelegate; + + /** Delegate for handling key up events not handled by the browser. */ + FOnUnhandledKeyUp UnhandledKeyUpDelegate; + + /** Delegate for handling key char events not handled by the browser. */ + FOnUnhandledKeyChar UnhandledKeyCharDelegate; + + FOnDragWindow DragWindowDelegate; + + bool bJSBindingToLoweringEnabled; + FNativeInterfaceJSScriptingPtr Scripting; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/SWebInterfaceBrowser.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/SWebInterfaceBrowser.cpp new file mode 100644 index 0000000000000000000000000000000000000000..41a8581384f15c960a830a510e6df61f634fad0f --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/SWebInterfaceBrowser.cpp @@ -0,0 +1,399 @@ +// Engine/Source/Runtime/WebBrowser/Private/SWebBrowser.cpp + +#include "SWebInterfaceBrowser.h" +#include "Widgets/Text/STextBlock.h" +#include "Widgets/Input/SEditableTextBox.h" +#include "Widgets/Input/SButton.h" +#include "Widgets/Images/SThrobber.h" + +#define LOCTEXT_NAMESPACE "WebInterfaceBrowser" + +SWebInterfaceBrowser::SWebInterfaceBrowser() +{ +} + +SWebInterfaceBrowser::~SWebInterfaceBrowser() +{ +} + +void SWebInterfaceBrowser::Construct(const FArguments& InArgs, const TSharedPtr<IWebInterfaceBrowserWindow>& BrowserWindow) +{ + OnLoadCompleted = InArgs._OnLoadCompleted; + OnLoadError = InArgs._OnLoadError; + OnLoadStarted = InArgs._OnLoadStarted; + OnTitleChanged = InArgs._OnTitleChanged; + OnUrlChanged = InArgs._OnUrlChanged; + OnBeforeNavigation = InArgs._OnBeforeNavigation; + OnLoadUrl = InArgs._OnLoadUrl; + OnShowDialog = InArgs._OnShowDialog; + OnDismissAllDialogs = InArgs._OnDismissAllDialogs; + OnBeforePopup = InArgs._OnBeforePopup; + OnConsoleMessage = InArgs._OnConsoleMessage; + OnCreateWindow = InArgs._OnCreateWindow; + OnCloseWindow = InArgs._OnCloseWindow; + bShowInitialThrobber = InArgs._ShowInitialThrobber; + + ChildSlot + [ + SNew(SVerticalBox) + +SVerticalBox::Slot() + .AutoHeight() + [ + SNew(SHorizontalBox) + .Visibility((InArgs._ShowControls || InArgs._ShowAddressBar) ? EVisibility::Visible : EVisibility::Collapsed) + + SHorizontalBox::Slot() + .Padding(0, 5) + .AutoWidth() + [ + SNew(SHorizontalBox) + .Visibility(InArgs._ShowControls ? EVisibility::Visible : EVisibility::Collapsed) + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("Back","Back")) + .IsEnabled(this, &SWebInterfaceBrowser::CanGoBack) + .OnClicked(this, &SWebInterfaceBrowser::OnBackClicked) + ] + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(LOCTEXT("Forward", "Forward")) + .IsEnabled(this, &SWebInterfaceBrowser::CanGoForward) + .OnClicked(this, &SWebInterfaceBrowser::OnForwardClicked) + ] + +SHorizontalBox::Slot() + .AutoWidth() + [ + SNew(SButton) + .Text(this, &SWebInterfaceBrowser::GetReloadButtonText) + .OnClicked(this, &SWebInterfaceBrowser::OnReloadClicked) + ] + +SHorizontalBox::Slot() + .FillWidth(1.0f) + .VAlign(VAlign_Center) + .HAlign(HAlign_Right) + .Padding(5) + [ + SNew(STextBlock) + .Visibility(InArgs._ShowAddressBar ? EVisibility::Collapsed : EVisibility::Visible ) + .Text(this, &SWebInterfaceBrowser::GetTitleText) + .Justification(ETextJustify::Right) + ] + ] + +SHorizontalBox::Slot() + .VAlign(VAlign_Center) + .HAlign(HAlign_Fill) + .Padding(5.f, 5.f) + [ + // @todo: A proper addressbar widget should go here, for now we use a simple textbox. + SAssignNew(InputText, SEditableTextBox) + .Visibility(InArgs._ShowAddressBar ? EVisibility::Visible : EVisibility::Collapsed) + .OnTextCommitted(this, &SWebInterfaceBrowser::OnUrlTextCommitted) + .Text(this, &SWebInterfaceBrowser::GetAddressBarUrlText) + .SelectAllTextWhenFocused(true) + .ClearKeyboardFocusOnCommit(true) + .RevertTextOnEscape(true) + ] + ] + +SVerticalBox::Slot() + [ + SNew(SOverlay) + + SOverlay::Slot() + [ + SAssignNew(BrowserView, SWebInterfaceBrowserView, BrowserWindow) + .ParentWindow(InArgs._ParentWindow) + .InitialURL(InArgs._InitialURL) + .ContentsToLoad(InArgs._ContentsToLoad) + .ShowErrorMessage(InArgs._ShowErrorMessage) + .SupportsTransparency(InArgs._SupportsTransparency) + .SupportsThumbMouseButtonNavigation(InArgs._SupportsThumbMouseButtonNavigation) + .BackgroundColor(InArgs._BackgroundColor) + .PopupMenuMethod(InArgs._PopupMenuMethod) + .ViewportSize(InArgs._ViewportSize) + .OnLoadCompleted(OnLoadCompleted) + .OnLoadError(OnLoadError) + .OnLoadStarted(OnLoadStarted) + .OnTitleChanged(OnTitleChanged) + .OnUrlChanged(OnUrlChanged) + .OnBeforePopup(OnBeforePopup) + .OnCreateWindow(OnCreateWindow) + .OnCloseWindow(OnCloseWindow) + .OnBeforeNavigation(OnBeforeNavigation) + .OnLoadUrl(OnLoadUrl) + .OnShowDialog(OnShowDialog) + .OnDismissAllDialogs(OnDismissAllDialogs) + .Visibility(this, &SWebInterfaceBrowser::GetViewportVisibility) + .OnSuppressContextMenu(InArgs._OnSuppressContextMenu) + .OnDragWindow(InArgs._OnDragWindow) + .OnConsoleMessage(OnConsoleMessage) + .BrowserFrameRate(InArgs._BrowserFrameRate) + ] + + SOverlay::Slot() + .HAlign(HAlign_Center) + .VAlign(VAlign_Center) + [ + SNew(SCircularThrobber) + .Radius(10.0f) + .ToolTipText(LOCTEXT("LoadingThrobberToolTip", "Loading page...")) + .Visibility(this, &SWebInterfaceBrowser::GetLoadingThrobberVisibility) + ] + ] + ]; +} + +void SWebInterfaceBrowser::LoadURL(FString NewURL) +{ + if (BrowserView.IsValid()) + { + BrowserView->LoadURL(NewURL); + } +} + +void SWebInterfaceBrowser::LoadString(FString Contents, FString DummyURL) +{ + if (BrowserView.IsValid()) + { + BrowserView->LoadString(Contents, DummyURL); + } +} + +void SWebInterfaceBrowser::Reload() +{ + if (BrowserView.IsValid()) + { + BrowserView->Reload(); + } +} + +void SWebInterfaceBrowser::StopLoad() +{ + if (BrowserView.IsValid()) + { + BrowserView->StopLoad(); + } +} + +FText SWebInterfaceBrowser::GetTitleText() const +{ + if (BrowserView.IsValid()) + { + return BrowserView->GetTitleText(); + } + return LOCTEXT("InvalidWindow", "Browser Window is not valid/supported"); +} + +FString SWebInterfaceBrowser::GetUrl() const +{ + if (BrowserView.IsValid()) + { + return BrowserView->GetUrl(); + } + + return FString(); +} + +FText SWebInterfaceBrowser::GetAddressBarUrlText() const +{ + if(BrowserView.IsValid()) + { + return BrowserView->GetAddressBarUrlText(); + } + return FText::GetEmpty(); +} + +bool SWebInterfaceBrowser::IsLoaded() const +{ + if (BrowserView.IsValid()) + { + return BrowserView->IsLoaded(); + } + + return false; +} + +bool SWebInterfaceBrowser::IsLoading() const +{ + if (BrowserView.IsValid()) + { + return BrowserView->IsLoading(); + } + + return false; +} + +bool SWebInterfaceBrowser::CanGoBack() const +{ + if (BrowserView.IsValid()) + { + return BrowserView->CanGoBack(); + } + return false; +} + +void SWebInterfaceBrowser::GoBack() +{ + if (BrowserView.IsValid()) + { + BrowserView->GoBack(); + } +} + +FReply SWebInterfaceBrowser::OnBackClicked() +{ + GoBack(); + return FReply::Handled(); +} + +bool SWebInterfaceBrowser::CanGoForward() const +{ + if (BrowserView.IsValid()) + { + return BrowserView->CanGoForward(); + } + return false; +} + +void SWebInterfaceBrowser::GoForward() +{ + if (BrowserView.IsValid()) + { + BrowserView->GoForward(); + } +} + +FReply SWebInterfaceBrowser::OnForwardClicked() +{ + GoForward(); + return FReply::Handled(); +} + +FText SWebInterfaceBrowser::GetReloadButtonText() const +{ + static FText ReloadText = LOCTEXT("Reload", "Reload"); + static FText StopText = LOCTEXT("StopText", "Stop"); + + if (BrowserView.IsValid()) + { + if (BrowserView->IsLoading()) + { + return StopText; + } + } + return ReloadText; +} + +FReply SWebInterfaceBrowser::OnReloadClicked() +{ + if (IsLoading()) + { + StopLoad(); + } + else + { + Reload(); + } + return FReply::Handled(); +} + +void SWebInterfaceBrowser::OnUrlTextCommitted( const FText& NewText, ETextCommit::Type CommitType ) +{ + if(CommitType == ETextCommit::OnEnter) + { + LoadURL(NewText.ToString()); + } +} + +EVisibility SWebInterfaceBrowser::GetViewportVisibility() const +{ + if (!bShowInitialThrobber || BrowserView->IsInitialized()) + { + return EVisibility::Visible; + } + return EVisibility::Hidden; +} + +EVisibility SWebInterfaceBrowser::GetLoadingThrobberVisibility() const +{ + if (bShowInitialThrobber && !BrowserView->IsInitialized()) + { + return EVisibility::Visible; + } + return EVisibility::Hidden; +} + + +void SWebInterfaceBrowser::ExecuteJavascript(const FString& ScriptText) +{ + if (BrowserView.IsValid()) + { + BrowserView->ExecuteJavascript(ScriptText); + } +} + +void SWebInterfaceBrowser::GetSource(TFunction<void (const FString&)> Callback) const +{ + if (BrowserView.IsValid()) + { + BrowserView->GetSource(Callback); + } +} + +void SWebInterfaceBrowser::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + if (BrowserView.IsValid()) + { + BrowserView->BindUObject(Name, Object, bIsPermanent); + } +} + +void SWebInterfaceBrowser::UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + if (BrowserView.IsValid()) + { + BrowserView->UnbindUObject(Name, Object, bIsPermanent); + } +} + +void SWebInterfaceBrowser::BindAdapter(const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter) +{ + if (BrowserView.IsValid()) + { + BrowserView->BindAdapter(Adapter); + } +} + +void SWebInterfaceBrowser::UnbindAdapter(const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter) +{ + if (BrowserView.IsValid()) + { + BrowserView->UnbindAdapter(Adapter); + } +} + +void SWebInterfaceBrowser::BindInputMethodSystem(ITextInputMethodSystem* TextInputMethodSystem) +{ + if (BrowserView.IsValid()) + { + BrowserView->BindInputMethodSystem(TextInputMethodSystem); + } +} + +void SWebInterfaceBrowser::UnbindInputMethodSystem() +{ + if (BrowserView.IsValid()) + { + BrowserView->UnbindInputMethodSystem(); + } +} + +void SWebInterfaceBrowser::SetParentWindow(TSharedPtr<SWindow> Window) +{ + if (BrowserView.IsValid()) + { + BrowserView->SetParentWindow(Window); + } +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/SWebInterfaceBrowserView.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/SWebInterfaceBrowserView.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0772ea9e6d5a7a09051f4a3482ff9543d4b07b62 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/SWebInterfaceBrowserView.cpp @@ -0,0 +1,755 @@ +// Engine/Source/Runtime/WebBrowser/Private/SWebBrowserView.cpp + +#include "SWebInterfaceBrowserView.h" +#include "Misc/CommandLine.h" +#include "Misc/ConfigCacheIni.h" +#include "Containers/Ticker.h" +#include "WebInterfaceBrowserModule.h" +#include "Layout/WidgetPath.h" +#include "Framework/Application/MenuStack.h" +#include "Framework/Application/SlateApplication.h" +#include "IWebInterfaceBrowserDialog.h" +#include "IWebInterfaceBrowserWindow.h" +#include "WebInterfaceBrowserViewport.h" +#include "IWebInterfaceBrowserAdapter.h" + +#if WITH_CEF3 +# include "CEF/CEFWebInterfaceBrowserWindow.h" +#else +# define DUMMY_WEB_BROWSER 1 +#endif + +#define LOCTEXT_NAMESPACE "WebInterfaceBrowser" + +SWebInterfaceBrowserView::SWebInterfaceBrowserView() +{ +} + +SWebInterfaceBrowserView::~SWebInterfaceBrowserView() +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->OnDocumentStateChanged().RemoveAll(this); + BrowserWindow->OnNeedsRedraw().RemoveAll(this); + BrowserWindow->OnTitleChanged().RemoveAll(this); + BrowserWindow->OnUrlChanged().RemoveAll(this); + BrowserWindow->OnToolTip().RemoveAll(this); + BrowserWindow->OnShowPopup().RemoveAll(this); + BrowserWindow->OnDismissPopup().RemoveAll(this); + + BrowserWindow->OnShowDialog().Unbind(); + BrowserWindow->OnDismissAllDialogs().Unbind(); + BrowserWindow->OnCreateWindow().Unbind(); + BrowserWindow->OnCloseWindow().Unbind(); + BrowserWindow->OnSuppressContextMenu().Unbind(); + BrowserWindow->OnDragWindow().Unbind(); + BrowserWindow->OnConsoleMessage().Unbind(); + + if (BrowserWindow->OnBeforeBrowse().IsBoundToObject(this)) + { + BrowserWindow->OnBeforeBrowse().Unbind(); + } + + if (BrowserWindow->OnLoadUrl().IsBoundToObject(this)) + { + BrowserWindow->OnLoadUrl().Unbind(); + } + + if (BrowserWindow->OnBeforePopup().IsBoundToObject(this)) + { + BrowserWindow->OnBeforePopup().Unbind(); + } + } + + TSharedPtr<SWindow> SlateParentWindow = SlateParentWindowPtr.Pin(); + if (SlateParentWindow.IsValid()) + { + SlateParentWindow->GetOnWindowDeactivatedEvent().RemoveAll(this); + } + + if (SlateParentWindow.IsValid()) + { + SlateParentWindow->GetOnWindowActivatedEvent().RemoveAll(this); + } +} + +void SWebInterfaceBrowserView::Construct(const FArguments& InArgs, const TSharedPtr<IWebInterfaceBrowserWindow>& InWebBrowserWindow) +{ + OnLoadCompleted = InArgs._OnLoadCompleted; + OnLoadError = InArgs._OnLoadError; + OnLoadStarted = InArgs._OnLoadStarted; + OnTitleChanged = InArgs._OnTitleChanged; + OnUrlChanged = InArgs._OnUrlChanged; + OnBeforeNavigation = InArgs._OnBeforeNavigation; + OnLoadUrl = InArgs._OnLoadUrl; + OnShowDialog = InArgs._OnShowDialog; + OnDismissAllDialogs = InArgs._OnDismissAllDialogs; + OnBeforePopup = InArgs._OnBeforePopup; + OnCreateWindow = InArgs._OnCreateWindow; + OnCloseWindow = InArgs._OnCloseWindow; + AddressBarUrl = FText::FromString(InArgs._InitialURL); + PopupMenuMethod = InArgs._PopupMenuMethod; + OnUnhandledKeyDown = InArgs._OnUnhandledKeyDown; + OnUnhandledKeyUp = InArgs._OnUnhandledKeyUp; + OnUnhandledKeyChar = InArgs._OnUnhandledKeyChar; + OnConsoleMessage = InArgs._OnConsoleMessage; + + BrowserWindow = InWebBrowserWindow; + if(!BrowserWindow.IsValid()) + { + + static bool AllowCEF = !FParse::Param(FCommandLine::Get(), TEXT("nocef")); + bool bBrowserEnabled = true; + GConfig->GetBool(TEXT("Browser"), TEXT("bEnabled"), bBrowserEnabled, GEngineIni); + if (AllowCEF && bBrowserEnabled) + { + FCreateInterfaceBrowserWindowSettings Settings; + Settings.InitialURL = InArgs._InitialURL; + Settings.bUseTransparency = InArgs._SupportsTransparency; + Settings.bInterceptLoadRequests = InArgs._InterceptLoadRequests; + Settings.bThumbMouseButtonNavigation = InArgs._SupportsThumbMouseButtonNavigation; + Settings.ContentsToLoad = InArgs._ContentsToLoad; + Settings.bShowErrorMessage = InArgs._ShowErrorMessage; + Settings.BackgroundColor = InArgs._BackgroundColor; + Settings.BrowserFrameRate = InArgs._BrowserFrameRate; + Settings.Context = InArgs._ContextSettings; + Settings.AltRetryDomains = InArgs._AltRetryDomains; + + // IWebBrowserModule::Get() was already callled in WebBrowserWidgetModule.cpp so we don't need to force the load again here + if (IWebInterfaceBrowserModule::IsAvailable() && IWebInterfaceBrowserModule::Get().IsWebModuleAvailable()) + { + BrowserWindow = IWebInterfaceBrowserModule::Get().GetSingleton()->CreateBrowserWindow(Settings); + } + } + } + + SlateParentWindowPtr = InArgs._ParentWindow; + + if (BrowserWindow.IsValid()) + { +#ifndef DUMMY_WEB_BROWSER + // The inner widget creation is handled by the WebBrowserWindow implementation. + const auto& BrowserWidgetRef = static_cast<FWebInterfaceBrowserWindow*>(BrowserWindow.Get())->CreateWidget(); + ChildSlot + [ + BrowserWidgetRef + ]; + BrowserWidget = BrowserWidgetRef; +#endif + + if(OnCreateWindow.IsBound()) + { + BrowserWindow->OnCreateWindow().BindSP(this, &SWebInterfaceBrowserView::HandleCreateWindow); + } + + if(OnCloseWindow.IsBound()) + { + BrowserWindow->OnCloseWindow().BindSP(this, &SWebInterfaceBrowserView::HandleCloseWindow); + } + + BrowserWindow->OnDocumentStateChanged().AddSP(this, &SWebInterfaceBrowserView::HandleBrowserWindowDocumentStateChanged); + BrowserWindow->OnNeedsRedraw().AddSP(this, &SWebInterfaceBrowserView::HandleBrowserWindowNeedsRedraw); + BrowserWindow->OnTitleChanged().AddSP(this, &SWebInterfaceBrowserView::HandleTitleChanged); + BrowserWindow->OnUrlChanged().AddSP(this, &SWebInterfaceBrowserView::HandleUrlChanged); + BrowserWindow->OnToolTip().AddSP(this, &SWebInterfaceBrowserView::HandleToolTip); + OnCreateToolTip = InArgs._OnCreateToolTip; + + if (!BrowserWindow->OnBeforeBrowse().IsBound()) + { + BrowserWindow->OnBeforeBrowse().BindSP(this, &SWebInterfaceBrowserView::HandleBeforeNavigation); + } + else + { + check(!OnBeforeNavigation.IsBound()); + } + + if (!BrowserWindow->OnLoadUrl().IsBound()) + { + BrowserWindow->OnLoadUrl().BindSP(this, &SWebInterfaceBrowserView::HandleLoadUrl); + } + else + { + check(!OnLoadUrl.IsBound()); + } + + if (!BrowserWindow->OnBeforePopup().IsBound()) + { + BrowserWindow->OnBeforePopup().BindSP(this, &SWebInterfaceBrowserView::HandleBeforePopup); + } + else + { + check(!OnBeforePopup.IsBound()); + } + + if (!BrowserWindow->OnUnhandledKeyDown().IsBound()) + { + BrowserWindow->OnUnhandledKeyDown().BindSP(this, &SWebInterfaceBrowserView::UnhandledKeyDown); + } + + if (!BrowserWindow->OnUnhandledKeyUp().IsBound()) + { + BrowserWindow->OnUnhandledKeyUp().BindSP(this, &SWebInterfaceBrowserView::UnhandledKeyUp); + } + + if (!BrowserWindow->OnUnhandledKeyChar().IsBound()) + { + BrowserWindow->OnUnhandledKeyChar().BindSP(this, &SWebInterfaceBrowserView::UnhandledKeyChar); + } + + BrowserWindow->OnShowDialog().BindSP(this, &SWebInterfaceBrowserView::HandleShowDialog); + BrowserWindow->OnDismissAllDialogs().BindSP(this, &SWebInterfaceBrowserView::HandleDismissAllDialogs); + BrowserWindow->OnShowPopup().AddSP(this, &SWebInterfaceBrowserView::HandleShowPopup); + BrowserWindow->OnDismissPopup().AddSP(this, &SWebInterfaceBrowserView::HandleDismissPopup); + + BrowserWindow->OnSuppressContextMenu().BindSP(this, &SWebInterfaceBrowserView::HandleSuppressContextMenu); + + + OnSuppressContextMenu = InArgs._OnSuppressContextMenu; + + BrowserWindow->OnDragWindow().BindSP(this, &SWebInterfaceBrowserView::HandleDrag); + OnDragWindow = InArgs._OnDragWindow; + + if (!BrowserWindow->OnConsoleMessage().IsBound()) + { + BrowserWindow->OnConsoleMessage().BindSP(this, &SWebInterfaceBrowserView::HandleConsoleMessage); + } + + BrowserViewport = MakeShareable(new FWebInterfaceBrowserViewport(BrowserWindow)); +#if WITH_CEF3 + BrowserWidget->SetViewportInterface(BrowserViewport.ToSharedRef()); +#endif + // If we could not obtain the parent window during widget construction, we'll defer and keep trying. + SetupParentWindowHandlers(); + } + else + { + OnLoadError.ExecuteIfBound(); + } +} + +int32 SWebInterfaceBrowserView::OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const +{ + if (!SlateParentWindowPtr.IsValid()) + { + SWebInterfaceBrowserView* MutableThis = const_cast<SWebInterfaceBrowserView*>(this); + MutableThis->SetupParentWindowHandlers(); + } + + int32 Layer = SCompoundWidget::OnPaint(Args, AllottedGeometry, MyCullingRect, OutDrawElements, LayerId, InWidgetStyle, bParentEnabled); + + // Cache a reference to our parent window, if we didn't already reference it. + if (!SlateParentWindowPtr.IsValid()) + { + SWindow* ParentWindow = OutDrawElements.GetPaintWindow(); + + TSharedRef<SWindow> SlateParentWindowRef = StaticCastSharedRef<SWindow>(ParentWindow->AsShared()); + SlateParentWindowPtr = SlateParentWindowRef; + if (BrowserWindow.IsValid()) + { + BrowserWindow->SetParentWindow(SlateParentWindowRef); + } + } + + return Layer; +} + +void SWebInterfaceBrowserView::HandleWindowDeactivated() +{ + if (BrowserViewport.IsValid()) + { + BrowserViewport->OnFocusLost(FFocusEvent()); + } +} + +void SWebInterfaceBrowserView::HandleWindowActivated() +{ + if (BrowserViewport.IsValid()) + { + if (HasAnyUserFocusOrFocusedDescendants()) + { + BrowserViewport->OnFocusReceived(FFocusEvent()); + } + } +} + +void SWebInterfaceBrowserView::LoadURL(FString NewURL) +{ + AddressBarUrl = FText::FromString(NewURL); + if (BrowserWindow.IsValid()) + { + BrowserWindow->LoadURL(NewURL); + } +} + +void SWebInterfaceBrowserView::LoadString(FString Contents, FString DummyURL) +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->LoadString(Contents, DummyURL); + } +} + +void SWebInterfaceBrowserView::Reload() +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->Reload(); + } +} + +void SWebInterfaceBrowserView::StopLoad() +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->StopLoad(); + } +} + +FText SWebInterfaceBrowserView::GetTitleText() const +{ + if (BrowserWindow.IsValid()) + { + return FText::FromString(BrowserWindow->GetTitle()); + } + return LOCTEXT("InvalidWindow", "Browser Window is not valid/supported"); +} + +FString SWebInterfaceBrowserView::GetUrl() const +{ + if (BrowserWindow.IsValid()) + { + return BrowserWindow->GetUrl(); + } + + return FString(); +} + +FText SWebInterfaceBrowserView::GetAddressBarUrlText() const +{ + if(BrowserWindow.IsValid()) + { + return AddressBarUrl; + } + return FText::GetEmpty(); +} + +bool SWebInterfaceBrowserView::IsLoaded() const +{ + if (BrowserWindow.IsValid()) + { + return (BrowserWindow->GetDocumentLoadingState() == EWebInterfaceBrowserDocumentState::Completed); + } + + return false; +} + +bool SWebInterfaceBrowserView::IsLoading() const +{ + if (BrowserWindow.IsValid()) + { + return (BrowserWindow->GetDocumentLoadingState() == EWebInterfaceBrowserDocumentState::Loading); + } + + return false; +} + +bool SWebInterfaceBrowserView::CanGoBack() const +{ + if (BrowserWindow.IsValid()) + { + return BrowserWindow->CanGoBack(); + } + return false; +} + +void SWebInterfaceBrowserView::GoBack() +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->GoBack(); + } +} + +bool SWebInterfaceBrowserView::CanGoForward() const +{ + if (BrowserWindow.IsValid()) + { + return BrowserWindow->CanGoForward(); + } + return false; +} + +void SWebInterfaceBrowserView::GoForward() +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->GoForward(); + } +} + +bool SWebInterfaceBrowserView::IsInitialized() const +{ + return BrowserWindow.IsValid() && BrowserWindow->IsInitialized(); +} + +void SWebInterfaceBrowserView::SetupParentWindowHandlers() +{ + if (!SlateParentWindowPtr.IsValid()) + { + SlateParentWindowPtr = FSlateApplication::Get().FindWidgetWindow(SharedThis(this)); + + TSharedPtr<SWindow> SlateParentWindow = SlateParentWindowPtr.Pin(); + if (SlateParentWindow.IsValid() && BrowserWindow.IsValid()) + { + if (!SlateParentWindow->GetOnWindowDeactivatedEvent().IsBoundToObject(this)) + { + SlateParentWindow->GetOnWindowDeactivatedEvent().AddSP(this, &SWebInterfaceBrowserView::HandleWindowDeactivated); + } + + if (!SlateParentWindow->GetOnWindowActivatedEvent().IsBoundToObject(this)) + { + SlateParentWindow->GetOnWindowActivatedEvent().AddSP(this, &SWebInterfaceBrowserView::HandleWindowActivated); + } + + BrowserWindow->SetParentWindow(SlateParentWindow); + } + } +} + +void SWebInterfaceBrowserView::HandleBrowserWindowDocumentStateChanged(EWebInterfaceBrowserDocumentState NewState) +{ + switch (NewState) + { + case EWebInterfaceBrowserDocumentState::Completed: + { + if (BrowserWindow.IsValid()) + { + for (auto Adapter : Adapters) + { + Adapter->ConnectTo(BrowserWindow.ToSharedRef()); + } + } + + OnLoadCompleted.ExecuteIfBound(); + } + break; + + case EWebInterfaceBrowserDocumentState::Error: + OnLoadError.ExecuteIfBound(); + break; + + case EWebInterfaceBrowserDocumentState::Loading: + OnLoadStarted.ExecuteIfBound(); + break; + } +} + +void SWebInterfaceBrowserView::HandleBrowserWindowNeedsRedraw() +{ + if (FSlateApplication::Get().IsSlateAsleep()) + { + // Tell slate that the widget needs to wake up for one frame to get redrawn + RegisterActiveTimer(0.f, FWidgetActiveTimerDelegate::CreateLambda([this](double InCurrentTime, float InDeltaTime) { return EActiveTimerReturnType::Stop; })); + } +} + +void SWebInterfaceBrowserView::HandleTitleChanged( FString NewTitle ) +{ + const FText NewTitleText = FText::FromString(NewTitle); + OnTitleChanged.ExecuteIfBound(NewTitleText); +} + +void SWebInterfaceBrowserView::HandleUrlChanged( FString NewUrl ) +{ + AddressBarUrl = FText::FromString(NewUrl); + OnUrlChanged.ExecuteIfBound(AddressBarUrl); +} + +void SWebInterfaceBrowserView::CloseBrowser() +{ + BrowserWindow->CloseBrowser(true /*force*/, true /*block until closed*/); +} + +void SWebInterfaceBrowserView::HandleToolTip(FString ToolTipText) +{ + if(ToolTipText.IsEmpty()) + { + FSlateApplication::Get().CloseToolTip(); + SetToolTip(nullptr); + } + else if (OnCreateToolTip.IsBound()) + { + SetToolTip(OnCreateToolTip.Execute(FText::FromString(ToolTipText))); + FSlateApplication::Get().UpdateToolTip(true); + } + else + { + SetToolTipText(FText::FromString(ToolTipText)); + FSlateApplication::Get().UpdateToolTip(true); + } +} + +bool SWebInterfaceBrowserView::HandleBeforeNavigation(const FString& Url, const FWebNavigationRequest& Request) +{ + if(OnBeforeNavigation.IsBound()) + { + return OnBeforeNavigation.Execute(Url, Request); + } + return false; +} + +bool SWebInterfaceBrowserView::HandleLoadUrl(const FString& Method, const FString& Url, FString& OutResponse) +{ + if(OnLoadUrl.IsBound()) + { + return OnLoadUrl.Execute(Method, Url, OutResponse); + } + return false; +} + +EWebInterfaceBrowserDialogEventResponse SWebInterfaceBrowserView::HandleShowDialog(const TWeakPtr<IWebInterfaceBrowserDialog>& DialogParams) +{ + if(OnShowDialog.IsBound()) + { + return OnShowDialog.Execute(DialogParams); + } + return EWebInterfaceBrowserDialogEventResponse::Unhandled; +} + +void SWebInterfaceBrowserView::HandleDismissAllDialogs() +{ + OnDismissAllDialogs.ExecuteIfBound(); +} + + +bool SWebInterfaceBrowserView::HandleBeforePopup(FString URL, FString Target) +{ + if (OnBeforePopup.IsBound()) + { + return OnBeforePopup.Execute(URL, Target); + } + + return false; +} + +void SWebInterfaceBrowserView::ExecuteJavascript(const FString& ScriptText) +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->ExecuteJavascript(ScriptText); + } +} + +void SWebInterfaceBrowserView::GetSource(TFunction<void (const FString&)> Callback) const +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->GetSource(Callback); + } +} + + +bool SWebInterfaceBrowserView::HandleCreateWindow(const TWeakPtr<IWebInterfaceBrowserWindow>& NewBrowserWindow, const TWeakPtr<IWebInterfaceBrowserPopupFeatures>& PopupFeatures) +{ + if(OnCreateWindow.IsBound()) + { + return OnCreateWindow.Execute(NewBrowserWindow, PopupFeatures); + } + return false; +} + +bool SWebInterfaceBrowserView::HandleCloseWindow(const TWeakPtr<IWebInterfaceBrowserWindow>& NewBrowserWindow) +{ + if(OnCloseWindow.IsBound()) + { + return OnCloseWindow.Execute(NewBrowserWindow); + } + return false; +} + +void SWebInterfaceBrowserView::BindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->BindUObject(Name, Object, bIsPermanent); + } +} + +void SWebInterfaceBrowserView::UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent) +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->UnbindUObject(Name, Object, bIsPermanent); + } +} + +void SWebInterfaceBrowserView::BindAdapter(const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter) +{ + Adapters.Add(Adapter); + if (BrowserWindow.IsValid()) + { + Adapter->ConnectTo(BrowserWindow.ToSharedRef()); + } +} + +void SWebInterfaceBrowserView::UnbindAdapter(const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter) +{ + Adapters.Remove(Adapter); + if (BrowserWindow.IsValid()) + { + Adapter->DisconnectFrom(BrowserWindow.ToSharedRef()); + } +} + +void SWebInterfaceBrowserView::BindInputMethodSystem(ITextInputMethodSystem* TextInputMethodSystem) +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->BindInputMethodSystem(TextInputMethodSystem); + } +} + +void SWebInterfaceBrowserView::UnbindInputMethodSystem() +{ + if (BrowserWindow.IsValid()) + { + BrowserWindow->UnbindInputMethodSystem(); + } +} + +void SWebInterfaceBrowserView::HandleShowPopup(const FIntRect& PopupSize) +{ + check(!PopupMenuPtr.IsValid()) + + TSharedPtr<SViewport> MenuContent; + SAssignNew(MenuContent, SViewport) + .ViewportSize(PopupSize.Size()) + .EnableGammaCorrection(false) + .EnableBlending(false) + .IgnoreTextureAlpha(true) +#if WITH_CEF3 + .RenderTransform(this, &SWebInterfaceBrowserView::GetPopupRenderTransform) +#endif + .Visibility(EVisibility::Visible); + MenuViewport = MakeShareable(new FWebInterfaceBrowserViewport(BrowserWindow, true)); + MenuContent->SetViewportInterface(MenuViewport.ToSharedRef()); + FWidgetPath WidgetPath; + FSlateApplication::Get().GeneratePathToWidgetUnchecked(SharedThis(this), WidgetPath); + if (WidgetPath.IsValid()) + { + TSharedRef< SWidget > MenuContentRef = MenuContent.ToSharedRef(); + const FGeometry& BrowserGeometry = WidgetPath.Widgets.Last().Geometry; + const FVector2D NewPosition = BrowserGeometry.LocalToAbsolute(PopupSize.Min); + + + // Open the pop-up. The popup method will be queried from the widget path passed in. + TSharedPtr<IMenu> NewMenu = FSlateApplication::Get().PushMenu(SharedThis(this), WidgetPath, MenuContentRef, NewPosition, FPopupTransitionEffect( FPopupTransitionEffect::ComboButton ), false); + NewMenu->GetOnMenuDismissed().AddSP(this, &SWebInterfaceBrowserView::HandleMenuDismissed); + PopupMenuPtr = NewMenu; + } + +} + +TOptional <FSlateRenderTransform> SWebInterfaceBrowserView::GetPopupRenderTransform() const +{ + if (BrowserWindow.IsValid()) + { +#if !defined(DUMMY_WEB_BROWSER) && WITH_CEF3 + TOptional<FSlateRenderTransform> LocalRenderTransform = FSlateRenderTransform(); + if (static_cast<FWebInterfaceBrowserWindow*>(BrowserWindow.Get())->UsingAcceleratedPaint()) + { + // the accelerated renderer for CEF generates inverted textures (compared to the slate co-ord system), so flip it here + LocalRenderTransform = FSlateRenderTransform(Concatenate(FScale2D(1, -1), FVector2D(0, PopupMenuPtr.Pin()->GetContent()->GetDesiredSize().Y))); + } + return LocalRenderTransform; +#else + return FSlateRenderTransform(); +#endif + } + else + { + return FSlateRenderTransform(); + } +} + +void SWebInterfaceBrowserView::HandleMenuDismissed(TSharedRef<IMenu>) +{ + PopupMenuPtr.Reset(); +} + +void SWebInterfaceBrowserView::HandleDismissPopup() +{ + if (PopupMenuPtr.IsValid()) + { + PopupMenuPtr.Pin()->Dismiss(); + FSlateApplication::Get().SetKeyboardFocus(SharedThis(this), EFocusCause::SetDirectly); + } +} + +bool SWebInterfaceBrowserView::HandleSuppressContextMenu() +{ + if (OnSuppressContextMenu.IsBound()) + { + return OnSuppressContextMenu.Execute(); + } + + return false; +} + +bool SWebInterfaceBrowserView::HandleDrag(const FPointerEvent& MouseEvent) +{ + if (OnDragWindow.IsBound()) + { + return OnDragWindow.Execute(MouseEvent); + } + return false; +} + +bool SWebInterfaceBrowserView::UnhandledKeyDown(const FKeyEvent& KeyEvent) +{ + if (OnUnhandledKeyDown.IsBound()) + { + return OnUnhandledKeyDown.Execute(KeyEvent); + } + return false; +} + +bool SWebInterfaceBrowserView::UnhandledKeyUp(const FKeyEvent& KeyEvent) +{ + if (OnUnhandledKeyUp.IsBound()) + { + return OnUnhandledKeyUp.Execute(KeyEvent); + } + return false; +} + +bool SWebInterfaceBrowserView::UnhandledKeyChar(const FCharacterEvent& CharacterEvent) +{ + if (OnUnhandledKeyChar.IsBound()) + { + return OnUnhandledKeyChar.Execute(CharacterEvent); + } + return false; +} + + +void SWebInterfaceBrowserView::SetParentWindow(TSharedPtr<SWindow> Window) +{ + SetupParentWindowHandlers(); + if (BrowserWindow.IsValid()) + { + BrowserWindow->SetParentWindow(Window); + } +} + +void SWebInterfaceBrowserView::SetBrowserKeyboardFocus() +{ + BrowserWindow->OnFocus(HasAnyUserFocusOrFocusedDescendants(), false); +} + +void SWebInterfaceBrowserView::HandleConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebInterfaceBrowserConsoleLogSeverity Serverity) +{ + OnConsoleMessage.ExecuteIfBound(Message, Source, Line, Serverity); +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserAdapter.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserAdapter.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7efea1009b0c95b7378a7df172f19dbaf9dc9b03 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserAdapter.cpp @@ -0,0 +1,100 @@ +// Engine/Source/Runtime/WebBrowser/Private/WebBrowserAdapter.cpp + +#include "CoreMinimal.h" +#include "UObject/GCObject.h" +#include "IWebInterfaceBrowserWindow.h" +#include "IWebInterfaceBrowserAdapter.h" + +class FDefaultWebInterfaceBrowserAdapter + : public IWebInterfaceBrowserAdapter + , public FGCObject + +{ +public: + + virtual FString GetName() const override + { + return Name; + } + + virtual bool IsPermanent() const override + { + return bIsPermanent; + } + + virtual void ConnectTo(const TSharedRef<IWebInterfaceBrowserWindow>& BrowserWindow) override + { + if (JSBridge != nullptr) + { + BrowserWindow->BindUObject(Name, JSBridge, bIsPermanent); + } + + if (!ConnectScriptText.IsEmpty()) + { + BrowserWindow->ExecuteJavascript(ConnectScriptText); + } + } + + virtual void DisconnectFrom(const TSharedRef<IWebInterfaceBrowserWindow>& BrowserWindow) override + { + if (!DisconnectScriptText.IsEmpty()) + { + BrowserWindow->ExecuteJavascript(DisconnectScriptText); + } + + if (JSBridge != nullptr) + { + BrowserWindow->UnbindUObject(Name, JSBridge, bIsPermanent); + } + } + + // FGCObject API + virtual void AddReferencedObjects(FReferenceCollector& Collector) override + { + if (JSBridge != nullptr) + { + Collector.AddReferencedObject(JSBridge); + } + } + virtual FString GetReferencerName() const override + { + return TEXT("FDefaultWebInterfaceBrowserAdapter"); + } + +private: + + FDefaultWebInterfaceBrowserAdapter( + const FString InName, + const FString InConnectScriptText, + const FString InDisconnectScriptText, + UObject* InJSBridge, + const bool InIsPermanent) + : Name(InName) + , ConnectScriptText(InConnectScriptText) + , DisconnectScriptText(InDisconnectScriptText) + , JSBridge(InJSBridge) + , bIsPermanent(InIsPermanent) + { } + +private: + + const FString Name; + const FString ConnectScriptText; + const FString DisconnectScriptText; + + UObject* JSBridge; + + const bool bIsPermanent; + + friend FWebInterfaceBrowserAdapterFactory; +}; + +TSharedRef<IWebInterfaceBrowserAdapter> FWebInterfaceBrowserAdapterFactory::Create(const FString& Name, UObject* JSBridge, bool IsPermanent) +{ + return MakeShareable(new FDefaultWebInterfaceBrowserAdapter(Name, FString(), FString(), JSBridge, IsPermanent)); +} + +TSharedRef<IWebInterfaceBrowserAdapter> FWebInterfaceBrowserAdapterFactory::Create(const FString& Name, UObject* JSBridge, bool IsPermanent, const FString& ConnectScriptText, const FString& DisconnectScriptText) +{ + return MakeShareable(new FDefaultWebInterfaceBrowserAdapter(Name, ConnectScriptText, DisconnectScriptText, JSBridge, IsPermanent)); +} diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserLog.h b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserLog.h new file mode 100644 index 0000000000000000000000000000000000000000..8188b40a6cc900af661e5bc62d134536ffbf2929 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserLog.h @@ -0,0 +1,7 @@ +// Engine/Source/Runtime/WebBrowser/Private/WebBrowserLog.h + +#pragma once + +#include "CoreMinimal.h" + +DECLARE_LOG_CATEGORY_EXTERN(LogWebInterfaceBrowser, Log, All); diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserModule.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..75d1791e198847939e0906fa7ef93ae243249be6 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserModule.cpp @@ -0,0 +1,125 @@ +// Engine/Source/Runtime/WebBrowser/Private/WebBrowserModule.cpp + +#include "WebInterfaceBrowserModule.h" +#include "WebInterfaceBrowserLog.h" +#include "WebInterfaceBrowserSingleton.h" +#include "Misc/App.h" +#include "Misc/EngineVersion.h" +#include "Misc/Paths.h" +#if WITH_CEF3 +# include "CEF3Utils.h" +# if PLATFORM_MAC +# include "include/wrapper/cef_library_loader.h" +# define CEF3_BIN_DIR TEXT("Binaries/ThirdParty/CEF3") +# if PLATFORM_MAC_ARM64 +# define CEF3_FRAMEWORK_DIR CEF3_BIN_DIR TEXT("/Mac/Chromium Embedded Framework arm64.framework") +# else +# define CEF3_FRAMEWORK_DIR CEF3_BIN_DIR TEXT("/Mac/Chromium Embedded Framework x86.framework") +# endif +# define CEF3_FRAMEWORK_EXE CEF3_FRAMEWORK_DIR TEXT("/Chromium Embedded Framework") +# endif +#endif + +DEFINE_LOG_CATEGORY(LogWebInterfaceBrowser); + +static FWebInterfaceBrowserSingleton* WebBrowserSingleton = nullptr; + +FWebInterfaceBrowserInitSettings::FWebInterfaceBrowserInitSettings() + : ProductVersion(FString::Printf(TEXT("%s/%s UnrealEngine/%s Chrome/90.0.4430.212"), FApp::GetProjectName(), FApp::GetBuildVersion(), *FEngineVersion::Current().ToString())) +{ +} + +class FWebInterfaceBrowserModule : public IWebInterfaceBrowserModule +{ +private: + // IModuleInterface Interface + virtual void StartupModule() override; + virtual void ShutdownModule() override; + +public: + virtual bool IsWebModuleAvailable() const override; + virtual IWebInterfaceBrowserSingleton* GetSingleton() override; + virtual bool CustomInitialize(const FWebInterfaceBrowserInitSettings& WebBrowserInitSettings) override; + +private: +#if WITH_CEF3 + bool bLoadedCEFModule = false; +#if PLATFORM_MAC + // Dynamically load the CEF framework library. + CefScopedLibraryLoader *CEFLibraryLoader = nullptr; +#endif +#endif +}; + +IMPLEMENT_MODULE( FWebInterfaceBrowserModule, WebBrowserUI ); + +void FWebInterfaceBrowserModule::StartupModule() +{ +#if WITH_CEF3 + if (!IsRunningCommandlet()) + { + CEF3Utils::BackupCEF3Logfile(FPaths::ProjectLogDir()); + } + bLoadedCEFModule = CEF3Utils::LoadCEF3Modules(true); +#if PLATFORM_MAC + // Dynamically load the CEF framework library into this dylibs memory space. + // CEF now loads function pointers at runtime so we need this to be dylib specific. + CEFLibraryLoader = new CefScopedLibraryLoader(); + + FString CefFrameworkPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_FRAMEWORK_EXE)); + CefFrameworkPath = FPaths::ConvertRelativePathToFull(CefFrameworkPath); + + bool bLoaderInitialized = false; + if (!CEFLibraryLoader->LoadInMain(TCHAR_TO_ANSI(*CefFrameworkPath))) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Chromium loader initialization failed")); + } +#endif // PLATFORM_MAC +#endif +} + +void FWebInterfaceBrowserModule::ShutdownModule() +{ + if (WebBrowserSingleton != nullptr) + { + delete WebBrowserSingleton; + WebBrowserSingleton = nullptr; + } + +#if WITH_CEF3 + CEF3Utils::UnloadCEF3Modules(); +#if PLATFORM_MAC + delete CEFLibraryLoader; + CEFLibraryLoader = nullptr; +#endif // PLATFORM_MAC +#endif +} + +bool FWebInterfaceBrowserModule::CustomInitialize(const FWebInterfaceBrowserInitSettings& WebBrowserInitSettings) +{ + if (WebBrowserSingleton == nullptr) + { + WebBrowserSingleton = new FWebInterfaceBrowserSingleton(WebBrowserInitSettings); + return true; + } + return false; +} + +IWebInterfaceBrowserSingleton* FWebInterfaceBrowserModule::GetSingleton() +{ + if (WebBrowserSingleton == nullptr) + { + WebBrowserSingleton = new FWebInterfaceBrowserSingleton(FWebInterfaceBrowserInitSettings()); + } + return WebBrowserSingleton; +} + + +bool FWebInterfaceBrowserModule::IsWebModuleAvailable() const +{ +#if WITH_CEF3 + return bLoadedCEFModule; +#else + return true; +#endif +} diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserSingleton.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserSingleton.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2a8699ae0c039160bd80c0c53ce64ebef0d0377a --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserSingleton.cpp @@ -0,0 +1,939 @@ +// Engine/Source/Runtime/WebBrowser/Private/WebBrowserSingleton.cpp + +#include "WebInterfaceBrowserSingleton.h" +#include "Misc/Paths.h" +#include "GenericPlatform/GenericPlatformFile.h" +#include "Misc/CommandLine.h" +#include "Misc/ConfigCacheIni.h" +#include "Internationalization/Culture.h" +#include "Misc/App.h" +#include "WebInterfaceBrowserModule.h" +#include "Misc/EngineVersion.h" +#include "Framework/Application/SlateApplication.h" +#include "IWebInterfaceBrowserCookieManager.h" +#include "WebInterfaceBrowserLog.h" + +#if PLATFORM_WINDOWS +#include "Windows/WindowsHWrapper.h" +#endif + +#if WITH_CEF3 +#include "Misc/ScopeLock.h" +#include "Async/Async.h" +#include "HAL/PlatformApplicationMisc.h" +#include "CEF/CEFInterfaceBrowserApp.h" +#include "CEF/CEFInterfaceBrowserHandler.h" +#include "CEF/CEFWebInterfaceBrowserWindow.h" +#include "CEF/CEFInterfaceSchemeHandler.h" +#include "CEF/CEFInterfaceResourceContextHandler.h" +#include "CEF/CEFInterfaceBrowserClosureTask.h" +# if PLATFORM_WINDOWS +# include "Windows/AllowWindowsPlatformTypes.h" +# endif +# pragma push_macro("OVERRIDE") +# undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +# include "include/cef_app.h" +# include "include/cef_version.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +# pragma pop_macro("OVERRIDE") +# if PLATFORM_WINDOWS +# include "Windows/HideWindowsPlatformTypes.h" +# endif +#endif + +#if BUILD_EMBEDDED_APP +# include "Native/NativeWebInterfaceBrowserProxy.h" +#endif + +// Define some platform-dependent file locations +#if WITH_CEF3 +# define CEF3_BIN_DIR TEXT("Binaries/ThirdParty/CEF3") +# if PLATFORM_WINDOWS && PLATFORM_64BITS +# define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Win64/Resources") +# define CEF3_SUBPROCES_EXE TEXT("Binaries/Win64/EpicWebHelper.exe") +# elif PLATFORM_WINDOWS && PLATFORM_32BITS +# define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Win32/Resources") +# define CEF3_SUBPROCES_EXE TEXT("Binaries/Win32/EpicWebHelper.exe") +# elif PLATFORM_MAC +# if PLATFORM_MAC_ARM64 +# define CEF3_FRAMEWORK_DIR CEF3_BIN_DIR TEXT("/Mac/Chromium Embedded Framework arm64.framework") +# else +# define CEF3_FRAMEWORK_DIR CEF3_BIN_DIR TEXT("/Mac/Chromium Embedded Framework x86.framework") +# endif +# define CEF3_RESOURCES_DIR CEF3_FRAMEWORK_DIR TEXT("/Resources") +# define CEF3_SUBPROCES_EXE TEXT("Binaries/Mac/EpicWebHelper.app/Contents/MacOS/EpicWebHelper") +# elif PLATFORM_LINUX // @todo Linux +# define CEF3_RESOURCES_DIR CEF3_BIN_DIR TEXT("/Linux/Resources") +# define CEF3_SUBPROCES_EXE TEXT("Binaries/Linux/EpicWebHelper") +# endif + // Caching is enabled by default. +# ifndef CEF3_DEFAULT_CACHE +# define CEF3_DEFAULT_CACHE 1 +# endif +#endif + +FString FWebInterfaceBrowserSingleton::ApplicationCacheDir() const +{ +#if PLATFORM_MAC + // OSX wants caches in a separate location from other app data + static TCHAR Result[MAC_MAX_PATH] = TEXT(""); + if (!Result[0]) + { + SCOPED_AUTORELEASE_POOL; + NSString *CacheBaseDir = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex: 0]; + NSString* BundleID = [[NSBundle mainBundle] bundleIdentifier]; + if(!BundleID) + { + BundleID = [[NSProcessInfo processInfo] processName]; + } + check(BundleID); + + NSString* AppCacheDir = [CacheBaseDir stringByAppendingPathComponent: BundleID]; + FPlatformString::CFStringToTCHAR((CFStringRef)AppCacheDir, Result); + } + return FString(Result); +#else + // Other platforms use the application data directory + return FPaths::ProjectSavedDir(); +#endif +} + + +class FWebInterfaceBrowserWindowFactory + : public IWebInterfaceBrowserWindowFactory +{ +public: + + virtual ~FWebInterfaceBrowserWindowFactory() + { } + + virtual TSharedPtr<IWebInterfaceBrowserWindow> Create( + TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent, + TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo) override + { + return IWebInterfaceBrowserModule::Get().GetSingleton()->CreateBrowserWindow( + BrowserWindowParent, + BrowserWindowInfo); + } + + virtual TSharedPtr<IWebInterfaceBrowserWindow> Create( + void* OSWindowHandle, + FString InitialURL, + bool bUseTransparency, + bool bThumbMouseButtonNavigation, + bool bInterceptLoadRequests = false, + TOptional<FString> ContentsToLoad = TOptional<FString>(), + bool ShowErrorMessage = true, + FColor BackgroundColor = FColor(255, 255, 255, 255)) override + { + FCreateInterfaceBrowserWindowSettings Settings; + Settings.OSWindowHandle = OSWindowHandle; + Settings.InitialURL = MoveTemp(InitialURL); + Settings.bUseTransparency = bUseTransparency; + Settings.bThumbMouseButtonNavigation = bThumbMouseButtonNavigation; + Settings.ContentsToLoad = MoveTemp(ContentsToLoad); + Settings.bShowErrorMessage = ShowErrorMessage; + Settings.BackgroundColor = BackgroundColor; + Settings.bInterceptLoadRequests = bInterceptLoadRequests; + + return IWebInterfaceBrowserModule::Get().GetSingleton()->CreateBrowserWindow(Settings); + } +}; + +class FNoWebInterfaceBrowserWindowFactory + : public IWebInterfaceBrowserWindowFactory +{ +public: + + virtual ~FNoWebInterfaceBrowserWindowFactory() + { } + + virtual TSharedPtr<IWebInterfaceBrowserWindow> Create( + TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent, + TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo) override + { + return nullptr; + } + + virtual TSharedPtr<IWebInterfaceBrowserWindow> Create( + void* OSWindowHandle, + FString InitialURL, + bool bUseTransparency, + bool bThumbMouseButtonNavigation, + bool bInterceptLoadRequests = false, + TOptional<FString> ContentsToLoad = TOptional<FString>(), + bool ShowErrorMessage = true, + FColor BackgroundColor = FColor(255, 255, 255, 255)) override + { + return nullptr; + } +}; + +#if WITH_CEF3 +#if PLATFORM_MAC || PLATFORM_LINUX +class FInterfacePosixSignalPreserver +{ +public: + FInterfacePosixSignalPreserver() + { + struct sigaction Sigact; + for (uint32 i = 0; i < UE_ARRAY_COUNT(PreserveSignals); ++i) + { + FMemory::Memset(&Sigact, 0, sizeof(Sigact)); + if (sigaction(PreserveSignals[i], nullptr, &Sigact) != 0) + { + UE_LOG(LogWebInterfaceBrowser, Warning, TEXT("Failed to backup signal handler for %i."), PreserveSignals[i]); + } + OriginalSignalHandlers[i] = Sigact; + } + } + + ~FInterfacePosixSignalPreserver() + { + for (uint32 i = 0; i < UE_ARRAY_COUNT(PreserveSignals); ++i) + { + if(sigaction(PreserveSignals[i], &OriginalSignalHandlers[i], nullptr) != 0) + { + UE_LOG(LogWebInterfaceBrowser, Warning, TEXT("Failed to restore signal handler for %i."), PreserveSignals[i]); + } + } + } + +private: + // Backup the list of signals that CEF/Chromium overrides, derived from SetupSignalHandlers() in + // https://chromium.googlesource.com/chromium/src.git/+/2fc330d0b93d4bfd7bd04b9fdd3102e529901f91/services/service_manager/embedder/main.cc + const int PreserveSignals[13] = {SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGABRT, + SIGFPE, SIGSEGV, SIGALRM, SIGTERM, SIGCHLD, SIGBUS, SIGTRAP, SIGPIPE}; + + struct sigaction OriginalSignalHandlers[UE_ARRAY_COUNT(PreserveSignals)]; +}; + +#endif // PLATFORM_MAC || PLATFORM_LINUX +#endif // WITH_CEF3 + + +FWebInterfaceBrowserSingleton::FWebInterfaceBrowserSingleton(const FWebInterfaceBrowserInitSettings& WebBrowserInitSettings) +#if WITH_CEF3 + : WebBrowserWindowFactory(MakeShareable(new FWebInterfaceBrowserWindowFactory())) +#else + : WebBrowserWindowFactory(MakeShareable(new FNoWebInterfaceBrowserWindowFactory())) +#endif + , bDevToolsShortcutEnabled(UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG) + , bJSBindingsToLoweringEnabled(true) + , bAppIsFocused(false) +#if WITH_CEF3 + , bCEFInitialized(false) +#endif + , DefaultMaterial(nullptr) + , DefaultTranslucentMaterial(nullptr) +{ +#if WITH_CEF3 + + // Only enable CEF if we have CEF3, we are not running a commandlet without rendering (e.g. cooking assets) and it has not been explicitly disabled + // Disallow CEF if we never plan on rendering, ie, with CanEverRender. This includes servers + bAllowCEF = (!IsRunningCommandlet() || (IsAllowCommandletRendering() && FParse::Param(FCommandLine::Get(), TEXT("AllowCommandletCEF")))) && + FApp::CanEverRender() && !FParse::Param(FCommandLine::Get(), TEXT("nocef")); + if (bAllowCEF) + { + // The FWebBrowserSingleton must be initialized on the game thread + check(IsInGameThread()); + + // Provide CEF with command-line arguments. +#if PLATFORM_WINDOWS + CefMainArgs MainArgs(hInstance); +#else + CefMainArgs MainArgs; +#endif + + // Enable high-DPI support early in CEF startup. For this to work it also depends + // on FPlatformApplicationMisc::SetHighDPIMode() being called already which should happen by default + CefEnableHighDPISupport(); + + bool bWebGL = FParse::Param(FCommandLine::Get(), TEXT("webgl")); + bool bVerboseLogging = FParse::Param(FCommandLine::Get(), TEXT("cefverbose")) || FParse::Param(FCommandLine::Get(), TEXT("debuglog")); + // CEFBrowserApp implements application-level callbacks. + CEFBrowserApp = new FCEFInterfaceBrowserApp(bWebGL); + + // Specify CEF global settings here. + CefSettings Settings; + Settings.no_sandbox = true; + Settings.command_line_args_disabled = true; + Settings.external_message_pump = true; + //@todo change to threaded version instead of using external_message_pump & OnScheduleMessagePumpWork + Settings.multi_threaded_message_loop = false; + //Set the default background for browsers to be opaque black, this is used for windowed (not OSR) browsers + // setting it black here prevents the white flash on load + Settings.background_color = CefColorSetARGB(255, 0, 0, 0); + +#if PLATFORM_LINUX + Settings.windowless_rendering_enabled = true; +#endif + + FString CefLogFile(FPaths::Combine(*FPaths::ProjectLogDir(), TEXT("cef3.log"))); + CefLogFile = FPaths::ConvertRelativePathToFull(CefLogFile); + CefString(&Settings.log_file) = TCHAR_TO_WCHAR(*CefLogFile); + Settings.log_severity = bVerboseLogging ? LOGSEVERITY_VERBOSE : LOGSEVERITY_WARNING; + + uint16 DebugPort; + if(FParse::Value(FCommandLine::Get(), TEXT("cefdebug="), DebugPort)) + { + Settings.remote_debugging_port = DebugPort; + } + + // Specify locale from our settings + FString LocaleCode = GetCurrentLocaleCode(); + CefString(&Settings.locale) = TCHAR_TO_WCHAR(*LocaleCode); + + // Append engine version to the user agent string. + CefString(&Settings.user_agent_product) = TCHAR_TO_WCHAR(*WebBrowserInitSettings.ProductVersion); + +#if CEF3_DEFAULT_CACHE + // Enable on disk cache + FString CachePath(FPaths::Combine(ApplicationCacheDir(), TEXT("webcache"))); + CachePath = FPaths::ConvertRelativePathToFull(GenerateWebCacheFolderName(CachePath)); + CefString(&Settings.cache_path) = TCHAR_TO_WCHAR(*CachePath); +#endif + + // Specify path to resources + FString ResourcesPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_RESOURCES_DIR)); + ResourcesPath = FPaths::ConvertRelativePathToFull(ResourcesPath); + if (!FPaths::DirectoryExists(ResourcesPath)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Chromium Resources information not found at: %s."), *ResourcesPath); + } + CefString(&Settings.resources_dir_path) = TCHAR_TO_WCHAR(*ResourcesPath); + +#if !PLATFORM_MAC + // On Mac Chromium ignores custom locales dir. Files need to be stored in Resources folder in the app bundle + FString LocalesPath(FPaths::Combine(*ResourcesPath, TEXT("locales"))); + LocalesPath = FPaths::ConvertRelativePathToFull(LocalesPath); + if (!FPaths::DirectoryExists(LocalesPath)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Chromium Locales information not found at: %s."), *LocalesPath); + } + CefString(&Settings.locales_dir_path) = TCHAR_TO_WCHAR(*LocalesPath); +#else + // LocaleCode may contain region, which for some languages may make CEF unable to find the locale pak files + // In that case use the language name for CEF locale + FString LocalePakPath = ResourcesPath + TEXT("/") + LocaleCode.Replace(TEXT("-"), TEXT("_")) + TEXT(".lproj/locale.pak"); + if (!FPaths::FileExists(LocalePakPath)) + { + FCultureRef Culture = FInternationalization::Get().GetCurrentCulture(); + LocaleCode = Culture->GetTwoLetterISOLanguageName(); + LocalePakPath = ResourcesPath + TEXT("/") + LocaleCode + TEXT(".lproj/locale.pak"); + if (FPaths::FileExists(LocalePakPath)) + { + CefString(&Settings.locale) = TCHAR_TO_WCHAR(*LocaleCode); + } + } + + // Let CEF know where we have put the framework bundle as it is non-default + FString CefFrameworkPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_FRAMEWORK_DIR)); + CefFrameworkPath = FPaths::ConvertRelativePathToFull(CefFrameworkPath); + CefString(&Settings.framework_dir_path) = TCHAR_TO_WCHAR(*CefFrameworkPath); + CefString(&Settings.main_bundle_path) = TCHAR_TO_WCHAR(*CefFrameworkPath); +#endif + + // Specify path to sub process exe + FString SubProcessPath(FPaths::Combine(*FPaths::EngineDir(), CEF3_SUBPROCES_EXE)); + SubProcessPath = FPaths::ConvertRelativePathToFull(SubProcessPath); + + if (!IPlatformFile::GetPlatformPhysical().FileExists(*SubProcessPath)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("EpicWebHelper.exe not found, check that this program has been built and is placed in: %s."), *SubProcessPath); + } + CefString(&Settings.browser_subprocess_path) = TCHAR_TO_WCHAR(*SubProcessPath); + +#if PLATFORM_MAC || PLATFORM_LINUX + // this class automatically preserves the sigaction handlers we have set + FInterfacePosixSignalPreserver PosixSignalPreserver; +#endif + + // Initialize CEF. + bCEFInitialized = CefInitialize(MainArgs, Settings, CEFBrowserApp.get(), nullptr); + check(bCEFInitialized); + + // Set the thread name back to GameThread. + FPlatformProcess::SetThreadName(*FName(NAME_GameThread).GetPlainNameString()); + + DefaultCookieManager = FCefWebInterfaceCookieManagerFactory::Create(CefCookieManager::GetGlobalManager(nullptr)); + } +#endif +} + +#if WITH_CEF3 +void FWebInterfaceBrowserSingleton::WaitForTaskQueueFlush() +{ + // Keep pumping messages until we see the one below clear the queue + bTaskFinished = false; + CefPostTask(TID_UI, new FCEFInterfaceBrowserClosureTask(nullptr, [=]() + { + bTaskFinished = true; + })); + + const double StartWaitAppTime = FPlatformTime::Seconds(); + while (!bTaskFinished) + { + FPlatformProcess::Sleep(0.01); + // CEF needs the windows message pump run to be able to finish closing a browser, so run it manually here + if (FSlateApplication::IsInitialized()) + { + FSlateApplication::Get().PumpMessages(); + } + CefDoMessageLoopWork(); + // Wait at most 1 second for tasks to clear, in case CEF crashes/hangs during process lifetime + if (FPlatformTime::Seconds() - StartWaitAppTime > 1.0f) + { + break; // don't spin forever + } + } +} +#endif + + +FWebInterfaceBrowserSingleton::~FWebInterfaceBrowserSingleton() +{ +#if WITH_CEF3 + if (!bCEFInitialized) + return; // CEF failed to init so don't crash trying to shut it down + + if (bAllowCEF) + { + { + FScopeLock Lock(&WindowInterfacesCS); + // Force all existing browsers to close in case any haven't been deleted + for (int32 Index = 0; Index < WindowInterfaces.Num(); ++Index) + { + auto BrowserWindow = WindowInterfaces[Index].Pin(); + if (BrowserWindow.IsValid() && BrowserWindow->IsValid()) + { + // Call CloseBrowser directly on the Host object as FWebBrowserWindow::CloseBrowser is delayed + BrowserWindow->InternalCefBrowser->GetHost()->CloseBrowser(true); + } + } + // Clear this before CefShutdown() below + WindowInterfaces.Reset(); + } + + // Remove references to the scheme handler factories + CefClearSchemeHandlerFactories(); + for (const TPair<FString, CefRefPtr<CefRequestContext>>& RequestContextPair : RequestContexts) + { + RequestContextPair.Value->ClearSchemeHandlerFactories(); + } + // Clear this before CefShutdown() below + RequestContexts.Reset(); + + // make sure any handler before load delegates are unbound + for (const TPair <FString,CefRefPtr<FCEFInterfaceResourceContextHandler>>& HandlerPair : RequestResourceHandlers) + { + HandlerPair.Value->OnBeforeLoad().Unbind(); + } + // Clear this before CefShutdown() below + RequestResourceHandlers.Reset(); + // CefRefPtr takes care of delete + CEFBrowserApp = nullptr; + + WaitForTaskQueueFlush(); + + // Shut down CEF. + CefShutdown(); + } + bCEFInitialized = false; +#endif +} + +TSharedRef<IWebInterfaceBrowserWindowFactory> FWebInterfaceBrowserSingleton::GetWebBrowserWindowFactory() const +{ + return WebBrowserWindowFactory; +} + +TSharedPtr<IWebInterfaceBrowserWindow> FWebInterfaceBrowserSingleton::CreateBrowserWindow( + TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent, + TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo + ) +{ +#if WITH_CEF3 + if (bAllowCEF) + { + TOptional<FString> ContentsToLoad; + + bool bShowErrorMessage = BrowserWindowParent->IsShowingErrorMessages(); + bool bThumbMouseButtonNavigation = BrowserWindowParent->IsThumbMouseButtonNavigationEnabled(); + bool bUseTransparency = BrowserWindowParent->UseTransparency(); + bool bUsingAcceleratedPaint = BrowserWindowParent->UsingAcceleratedPaint(); + bool bUseNativeCursors = BrowserWindowParent->UseNativeCursors(); + FString InitialURL = WCHAR_TO_TCHAR(BrowserWindowInfo->Browser->GetMainFrame()->GetURL().ToWString().c_str()); + TSharedPtr<FCEFWebInterfaceBrowserWindow> NewBrowserWindow(new FCEFWebInterfaceBrowserWindow(BrowserWindowInfo->Browser, BrowserWindowInfo->Handler, InitialURL, ContentsToLoad, bShowErrorMessage, bThumbMouseButtonNavigation, bUseTransparency, bUseNativeCursors, bJSBindingsToLoweringEnabled, bUsingAcceleratedPaint)); + BrowserWindowInfo->Handler->SetBrowserWindow(NewBrowserWindow); + { + FScopeLock Lock(&WindowInterfacesCS); + WindowInterfaces.Add(NewBrowserWindow); + } + NewBrowserWindow->GetCefBrowser()->GetHost()->SetWindowlessFrameRate(BrowserWindowParent->GetCefBrowser()->GetHost()->GetWindowlessFrameRate()); + return NewBrowserWindow; + } +#endif + return nullptr; +} + +TSharedPtr<IWebInterfaceBrowserWindow> FWebInterfaceBrowserSingleton::CreateBrowserWindow(const FCreateInterfaceBrowserWindowSettings& WindowSettings) +{ + bool bBrowserEnabled = true; + GConfig->GetBool(TEXT("Browser"), TEXT("bEnabled"), bBrowserEnabled, GEngineIni); + if (!bBrowserEnabled || !FApp::CanEverRender()) + { + return nullptr; + } + +#if WITH_CEF3 + if (bAllowCEF) + { + // Information used when creating the native window. + CefWindowInfo WindowInfo; + + // Specify CEF browser settings here. + CefBrowserSettings BrowserSettings; + + // The color to paint before a document is loaded + // if using a windowed(native) browser window AND bUseTransparency is true then the background actually uses Settings.background_color from above + // if using a OSR window and bUseTransparency is true then you get a transparency channel in your BGRA OnPaint + // if bUseTransparency is false then you get the background color defined by your RGB setting here + BrowserSettings.background_color = CefColorSetARGB(WindowSettings.bUseTransparency ? 0 : WindowSettings.BackgroundColor.A, WindowSettings.BackgroundColor.R, WindowSettings.BackgroundColor.G, WindowSettings.BackgroundColor.B); + + // Disable plugins + BrowserSettings.plugins = STATE_DISABLED; + + +#if PLATFORM_WINDOWS + // Create the widget as a child window on windows when passing in a parent window + if (WindowSettings.OSWindowHandle != nullptr) + { + RECT ClientRect = { 0, 0, 0, 0 }; + if (!GetClientRect((HWND)WindowSettings.OSWindowHandle, &ClientRect)) + { + UE_LOG(LogWebInterfaceBrowser, Error, TEXT("Failed to get client rect")); + } + WindowInfo.SetAsChild((CefWindowHandle)WindowSettings.OSWindowHandle, ClientRect); + } + else +#endif + { + // Use off screen rendering so we can integrate with our windows + WindowInfo.SetAsWindowless(kNullWindowHandle); + WindowInfo.shared_texture_enabled = WindowSettings.bAcceleratedPaint && FCEFWebInterfaceBrowserWindow::CanSupportAcceleratedPaint() ? 1 : 0; + BrowserSettings.windowless_frame_rate = WindowSettings.BrowserFrameRate; + } + + TArray<FString> AuthorizationHeaderAllowListURLS; + GConfig->GetArray(TEXT("Browser"), TEXT("AuthorizationHeaderAllowListURLS"), AuthorizationHeaderAllowListURLS, GEngineIni); + + // WebBrowserHandler implements browser-level callbacks. + CefRefPtr<FCEFInterfaceBrowserHandler> NewHandler(new FCEFInterfaceBrowserHandler(WindowSettings.bUseTransparency, WindowSettings.bInterceptLoadRequests ,WindowSettings.AltRetryDomains, AuthorizationHeaderAllowListURLS)); + + CefRefPtr<CefRequestContext> RequestContext = nullptr; + if (WindowSettings.Context.IsSet()) + { + const FInterfaceBrowserContextSettings Context = WindowSettings.Context.GetValue(); + const CefRefPtr<CefRequestContext>* ExistingRequestContext = RequestContexts.Find(Context.Id); + + if (ExistingRequestContext == nullptr) + { + CefRequestContextSettings RequestContextSettings; + CefString(&RequestContextSettings.accept_language_list) = Context.AcceptLanguageList.IsEmpty() ? TCHAR_TO_WCHAR(*GetCurrentLocaleCode()) : TCHAR_TO_WCHAR(*Context.AcceptLanguageList); + CefString(&RequestContextSettings.cache_path) = TCHAR_TO_WCHAR(*GenerateWebCacheFolderName(Context.CookieStorageLocation)); + RequestContextSettings.persist_session_cookies = Context.bPersistSessionCookies; + RequestContextSettings.ignore_certificate_errors = Context.bIgnoreCertificateErrors; + + CefRefPtr<FCEFInterfaceResourceContextHandler> ResourceContextHandler = new FCEFInterfaceResourceContextHandler(this); + ResourceContextHandler->OnBeforeLoad() = Context.OnBeforeContextResourceLoad; + RequestResourceHandlers.Add(Context.Id, ResourceContextHandler); + + //Create a new one + RequestContext = CefRequestContext::CreateContext(RequestContextSettings, ResourceContextHandler); + RequestContexts.Add(Context.Id, RequestContext); + } + else + { + RequestContext = *ExistingRequestContext; + } + SchemeHandlerFactories.RegisterFactoriesWith(RequestContext); + UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Creating browser for ContextId=%s."), *WindowSettings.Context.GetValue().Id); + } + if (RequestContext == nullptr) + { + // As of CEF drop 4430 the CreateBrowserSync call requires a non-null request context, so fall back to the default one if needed + RequestContext = CefRequestContext::GetGlobalContext(); + } + + // Create the CEF browser window. + CefRefPtr<CefBrowser> Browser = CefBrowserHost::CreateBrowserSync(WindowInfo, NewHandler.get(), TCHAR_TO_WCHAR(*WindowSettings.InitialURL), BrowserSettings, nullptr, RequestContext); + if (Browser.get()) + { + // Create new window + TSharedPtr<FCEFWebInterfaceBrowserWindow> NewBrowserWindow = MakeShareable(new FCEFWebInterfaceBrowserWindow( + Browser, + NewHandler, + WindowSettings.InitialURL, + WindowSettings.ContentsToLoad, + WindowSettings.bShowErrorMessage, + WindowSettings.bThumbMouseButtonNavigation, + WindowSettings.bUseTransparency, + WindowSettings.bUseNativeCursors, + bJSBindingsToLoweringEnabled, + WindowInfo.shared_texture_enabled == 1 ? true : false)); + NewHandler->SetBrowserWindow(NewBrowserWindow); + { + FScopeLock Lock(&WindowInterfacesCS); + WindowInterfaces.Add(NewBrowserWindow); + } + + return NewBrowserWindow; + } + } +#endif + return nullptr; +} + +#if BUILD_EMBEDDED_APP +TSharedPtr<IWebInterfaceBrowserWindow> FWebInterfaceBrowserSingleton::CreateNativeBrowserProxy() +{ + TSharedPtr<FNativeWebInterfaceBrowserProxy> NewBrowserWindow = MakeShareable(new FNativeWebInterfaceBrowserProxy( + bJSBindingsToLoweringEnabled + )); + NewBrowserWindow->Initialize(); + return NewBrowserWindow; +} +#endif //BUILD_EMBEDDED_APP + +bool FWebInterfaceBrowserSingleton::Tick(float DeltaTime) +{ + QUICK_SCOPE_CYCLE_COUNTER(STAT_FWebInterfaceBrowserSingleton_Tick); + +#if WITH_CEF3 + if (bAllowCEF) + { + { + FScopeLock Lock(&WindowInterfacesCS); + bool bIsSlateAwake = FSlateApplication::IsInitialized() && !FSlateApplication::Get().IsSlateAsleep(); + // Remove any windows that have been deleted and check whether it's currently visible + for (int32 Index = WindowInterfaces.Num() - 1; Index >= 0; --Index) + { + if (!WindowInterfaces[Index].IsValid()) + { + WindowInterfaces.RemoveAt(Index); + } + else if (bIsSlateAwake) // only check for Tick activity if Slate is currently ticking + { + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin(); + if(BrowserWindow.IsValid()) + { + // Test if we've ticked recently. If not assume the browser window has become hidden. + BrowserWindow->CheckTickActivity(); + } + } + } + } + + if (CEFBrowserApp != nullptr) + { + bool bForceMessageLoop = false; + GConfig->GetBool(TEXT("Browser"), TEXT("bForceMessageLoop"), bForceMessageLoop, GEngineIni); + + // Get the configured minimum hertz and make sure the value is within a reasonable range + static const int MaxFrameRateClamp = 60; + int32 MinMessageLoopHz = 1; + GConfig->GetInt(TEXT("Browser"), TEXT("MinMessageLoopHertz"), MinMessageLoopHz, GEngineIni); + MinMessageLoopHz = FMath::Clamp(MinMessageLoopHz, 1, 60); + + // Get the configured forced maximum hertz and make sure the value is within a reasonable range + int32 MaxForcedMessageLoopHz = 15; + GConfig->GetInt(TEXT("Browser"), TEXT("MaxForcedMessageLoopHertz"), MaxForcedMessageLoopHz, GEngineIni); + MaxForcedMessageLoopHz = FMath::Clamp(MaxForcedMessageLoopHz, MinMessageLoopHz, 60); + + // @todo: Hack: We rely on OnScheduleMessagePumpWork() which tells us to drive the CEF message pump, + // there appear to be some edge cases where we might not be getting a signal from it so for the time being + // we force a minimum rates here and let it run at a configurable maximum rate when we have any WindowInterfaces. + + // Convert to seconds which we'll use to compare against the time we accumulated since last pump / left till next pump + float MinMessageLoopSeconds = 1.0f / MinMessageLoopHz; + float MaxForcedMessageLoopSeconds = 1.0f / MaxForcedMessageLoopHz; + + static float SecondsSinceLastPump = 0; + static float SecondsSinceLastAppFocusCheck = MaxForcedMessageLoopSeconds; + static float SecondsToNextForcedPump = MaxForcedMessageLoopSeconds; + + // Accumulate time since last pump by adding DeltaTime which gives us the amount of time that has passed since last tick in seconds + SecondsSinceLastPump += DeltaTime; + SecondsSinceLastAppFocusCheck += DeltaTime; + // Time left till next pump + SecondsToNextForcedPump -= DeltaTime; + + bool bWantForce = bForceMessageLoop; // True if we wish to force message pump + bool bCanForce = SecondsToNextForcedPump <= 0; // But can we? + bool bMustForce = SecondsSinceLastPump >= MinMessageLoopSeconds; // Absolutely must force (Min frequency rate hit) + if (SecondsSinceLastAppFocusCheck > MinMessageLoopSeconds && WindowInterfaces.Num() > 0) + { + SecondsSinceLastAppFocusCheck = 0; + // only check app being foreground at the min message loop rate (1hz) and if we have a browser window to save CPU + bAppIsFocused = FPlatformApplicationMisc::IsThisApplicationForeground(); + } + // NOTE - bAppIsFocused could be stale if WindowInterfaces.Num() == 0 + bool bAppIsFocusedAndWebWindows = WindowInterfaces.Num() > 0 && bAppIsFocused; + + // if we won't force AND are the foreground OS app AND we have windows created see if any are visible (not minimized) right now + if (bWantForce == false && bMustForce == false && bAppIsFocusedAndWebWindows == true ) + { + for (int32 Index = 0; Index < WindowInterfaces.Num(); Index++) + { + if (WindowInterfaces[Index].IsValid()) + { + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin(); + if (BrowserWindow->GetParentWindow().IsValid()) + { + TSharedPtr<SWindow> BrowserParentWindow = BrowserWindow->GetParentWindow(); + if (!BrowserParentWindow->IsWindowMinimized()) + { + bWantForce = true; + } + } + } + } + } + + // tick the CEF app to determine when to run CefDoMessageLoopWork + if (CEFBrowserApp->TickMessagePump(DeltaTime, (bWantForce && bCanForce) || bMustForce)) + { + SecondsSinceLastPump = 0; + SecondsToNextForcedPump = MaxForcedMessageLoopSeconds; + } + } + + // Update video buffering for any windows that need it + for (int32 Index = 0; Index < WindowInterfaces.Num(); Index++) + { + if (WindowInterfaces[Index].IsValid()) + { + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin(); + if (BrowserWindow.IsValid()) + { + BrowserWindow->UpdateVideoBuffering(); + } + } + } + } +#endif + return true; +} + +FString FWebInterfaceBrowserSingleton::GetCurrentLocaleCode() +{ + FCultureRef Culture = FInternationalization::Get().GetCurrentCulture(); + FString LocaleCode = Culture->GetTwoLetterISOLanguageName(); + FString Country = Culture->GetRegion(); + if (!Country.IsEmpty()) + { + LocaleCode = LocaleCode + TEXT("-") + Country; + } + return LocaleCode; +} + +TSharedPtr<IWebInterfaceBrowserCookieManager> FWebInterfaceBrowserSingleton::GetCookieManager(TOptional<FString> ContextId) const +{ + if (ContextId.IsSet()) + { +#if WITH_CEF3 + if (bAllowCEF) + { + const CefRefPtr<CefRequestContext>* ExistingContext = RequestContexts.Find(ContextId.GetValue()); + + if (ExistingContext && ExistingContext->get()) + { + // Cache these cookie managers? + return FCefWebInterfaceCookieManagerFactory::Create((*ExistingContext)->GetCookieManager(nullptr)); + } + else + { + UE_LOG(LogWebInterfaceBrowser, Log, TEXT("No cookie manager for ContextId=%s. Using default cookie manager"), *ContextId.GetValue()); + } + } +#endif + } + // No ContextId or cookie manager instance associated with it. Use default + return DefaultCookieManager; +} + +#if WITH_CEF3 +bool FWebInterfaceBrowserSingleton::URLRequestAllowsCredentials(const FString& URL) +{ + FScopeLock Lock(&WindowInterfacesCS); + // The FCEFResourceContextHandler::OnBeforeResourceLoad call doesn't get the browser/frame associated with the load + // (because bugs) so just look at each browser and see if it thinks it knows about this URL + for (int32 Index = WindowInterfaces.Num() - 1; Index >= 0; --Index) + { + TSharedPtr<FCEFWebInterfaceBrowserWindow> BrowserWindow = WindowInterfaces[Index].Pin(); + if (BrowserWindow.IsValid() && BrowserWindow->URLRequestAllowsCredentials(URL)) + { + return true; + } + } + + return false; +} + +FString FWebInterfaceBrowserSingleton::GenerateWebCacheFolderName(const FString& InputPath) +{ + if (InputPath.IsEmpty()) + return InputPath; + + // append the version of this CEF build to our requested cache folder path + // this means each new CEF build gets its own cache folder, making downgrading safe + return InputPath + "_" + MAKE_STRING(CHROME_VERSION_BUILD); +} +#endif + +void FWebInterfaceBrowserSingleton::ClearOldCacheFolders(const FString &CachePathRoot, const FString &CachePrefix) +{ +#if WITH_CEF3 + // only CEF3 currently has version dependant cache folders that may need cleanup + struct FDirectoryVisitor : public IPlatformFile::FDirectoryVisitor + { + const FString CachePrefix; + const FString CurrentCachePath; + + FDirectoryVisitor(const FString &InCachePrefix, const FString &InCurrentCachePath) + : CachePrefix(InCachePrefix), + CurrentCachePath(InCurrentCachePath) + { + } + + virtual bool Visit(const TCHAR* FilenameOrDirectory, bool bIsDirectory) override + { + static const FString CachePrefixSearch = "/" + CachePrefix; + if (bIsDirectory) + { + FString DirName(FilenameOrDirectory); + if (DirName.Contains(CachePrefixSearch) && DirName.Equals(CurrentCachePath)==false) + { + UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Old Cache folder found=%s, deleting"), *DirName); + // BUGBUG - enable this deletion once we are happy with the new CEF version rollout + // Also consider adding code to preserve the previous versions folder for a while? + /*Async<void>(EAsyncExecution::ThreadPool, [DirName]() + { + IPlatformFile::GetPlatformPhysical().DeleteDirectoryRecursively(*DirName); + });*/ + + } + } + + return true; + } + }; + + // Enumerate the contents of the current directory + FDirectoryVisitor Visitor(CachePrefix, GenerateWebCacheFolderName(FPaths::Combine(CachePathRoot, CachePrefix))); + IPlatformFile::GetPlatformPhysical().IterateDirectory(*CachePathRoot, Visitor); + + +#endif +} + +bool FWebInterfaceBrowserSingleton::RegisterContext(const FInterfaceBrowserContextSettings& Settings) +{ +#if WITH_CEF3 + if (bAllowCEF) + { + const CefRefPtr<CefRequestContext>* ExistingContext = RequestContexts.Find(Settings.Id); + + if (ExistingContext != nullptr) + { + // You can't register the same context twice and + // you can't update the settings for a context that already exists + return false; + } + + CefRequestContextSettings RequestContextSettings; + CefString(&RequestContextSettings.accept_language_list) = Settings.AcceptLanguageList.IsEmpty() ? TCHAR_TO_WCHAR(*GetCurrentLocaleCode()) : TCHAR_TO_WCHAR(*Settings.AcceptLanguageList); + CefString(&RequestContextSettings.cache_path) = TCHAR_TO_WCHAR(*GenerateWebCacheFolderName(Settings.CookieStorageLocation)); + RequestContextSettings.persist_session_cookies = Settings.bPersistSessionCookies; + RequestContextSettings.ignore_certificate_errors = Settings.bIgnoreCertificateErrors; + + //Create a new one + CefRefPtr<FCEFInterfaceResourceContextHandler> ResourceContextHandler = new FCEFInterfaceResourceContextHandler(this); + ResourceContextHandler->OnBeforeLoad() = Settings.OnBeforeContextResourceLoad; + RequestResourceHandlers.Add(Settings.Id, ResourceContextHandler); + CefRefPtr<CefRequestContext> RequestContext = CefRequestContext::CreateContext(RequestContextSettings, ResourceContextHandler); + RequestContexts.Add(Settings.Id, RequestContext); + SchemeHandlerFactories.RegisterFactoriesWith(RequestContext); + UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Registering ContextId=%s."), *Settings.Id); + return true; + } +#endif + return false; +} + +bool FWebInterfaceBrowserSingleton::UnregisterContext(const FString& ContextId) +{ +#if WITH_CEF3 + bool bFoundContext = false; + if (bAllowCEF) + { + UE_LOG(LogWebInterfaceBrowser, Log, TEXT("Unregistering ContextId=%s."), *ContextId); + + WaitForTaskQueueFlush(); + + CefRefPtr<CefRequestContext> Context; + if (RequestContexts.RemoveAndCopyValue(ContextId, Context)) + { + bFoundContext = true; + Context->ClearSchemeHandlerFactories(); + } + + CefRefPtr<FCEFInterfaceResourceContextHandler> ResourceHandler; + if (RequestResourceHandlers.RemoveAndCopyValue(ContextId, ResourceHandler)) + { + ResourceHandler->OnBeforeLoad().Unbind(); + } + } + return bFoundContext; +#else + return false; +#endif +} + +bool FWebInterfaceBrowserSingleton::RegisterSchemeHandlerFactory(FString Scheme, FString Domain, IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory) +{ +#if WITH_CEF3 + if (bAllowCEF) + { + SchemeHandlerFactories.AddSchemeHandlerFactory(MoveTemp(Scheme), MoveTemp(Domain), WebBrowserSchemeHandlerFactory); + return true; + } +#endif + return false; +} + +bool FWebInterfaceBrowserSingleton::UnregisterSchemeHandlerFactory(IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory) +{ +#if WITH_CEF3 + if (bAllowCEF) + { + SchemeHandlerFactories.RemoveSchemeHandlerFactory(WebBrowserSchemeHandlerFactory); + return true; + } +#endif + return false; +} + +// Cleanup macros to avoid having them leak outside this source file +#undef CEF3_BIN_DIR +#undef CEF3_FRAMEWORK_DIR +#undef CEF3_RESOURCES_DIR +#undef CEF3_SUBPROCES_EXE diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserSingleton.h b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserSingleton.h new file mode 100644 index 0000000000000000000000000000000000000000..341c46e885dccd317b008b998e73c29d66f54f43 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserSingleton.h @@ -0,0 +1,217 @@ +// Engine/Source/Runtime/WebBrowser/Private/WebBrowserSingleton.h + +#pragma once + +#include "CoreMinimal.h" +#include "Containers/Ticker.h" +#include "IWebInterfaceBrowserSingleton.h" + +#if WITH_CEF3 +#if PLATFORM_WINDOWS + #include "Windows/WindowsHWrapper.h" + #include "Windows/AllowWindowsPlatformTypes.h" + #include "Windows/AllowWindowsPlatformAtomics.h" +#endif +#pragma push_macro("OVERRIDE") +#undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +#include "include/internal/cef_ptr.h" +#include "include/cef_request_context.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +#pragma pop_macro("OVERRIDE") +#if PLATFORM_WINDOWS + #include "Windows/HideWindowsPlatformAtomics.h" + #include "Windows/HideWindowsPlatformTypes.h" +#endif +#include "CEF/CEFInterfaceSchemeHandler.h" +#include "CEF/CEFInterfaceResourceContextHandler.h" +class CefListValue; +class FCEFInterfaceBrowserApp; +class FCEFWebInterfaceBrowserWindow; +#endif + +class IWebInterfaceBrowserCookieManager; +class IWebInterfaceBrowserWindow; +struct FWebInterfaceBrowserWindowInfo; +struct FWebInterfaceBrowserInitSettings; +class UMaterialInterface; + +PRAGMA_DISABLE_DEPRECATION_WARNINGS + +/** + * Implementation of singleton class that takes care of general web browser tasks + */ +class FWebInterfaceBrowserSingleton + : public IWebInterfaceBrowserSingleton + , public FTSTickerObjectBase +{ +public: + + /** Constructor. */ + FWebInterfaceBrowserSingleton(const FWebInterfaceBrowserInitSettings& WebBrowserInitSettings); + + /** Virtual destructor. */ + virtual ~FWebInterfaceBrowserSingleton(); + + /** + * Gets the Current Locale Code in the format CEF expects + * + * @return Locale code as either "xx" or "xx-YY" + */ + static FString GetCurrentLocaleCode(); + + virtual FString ApplicationCacheDir() const override; + +public: + + // IWebBrowserSingleton Interface + + virtual TSharedRef<IWebInterfaceBrowserWindowFactory> GetWebBrowserWindowFactory() const override; + + TSharedPtr<IWebInterfaceBrowserWindow> CreateBrowserWindow( + TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent, + TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo) override; + + TSharedPtr<IWebInterfaceBrowserWindow> CreateBrowserWindow(const FCreateInterfaceBrowserWindowSettings& Settings) override; + +#if BUILD_EMBEDDED_APP + TSharedPtr<IWebInterfaceBrowserWindow> CreateNativeBrowserProxy() override; +#endif + + virtual TSharedPtr<IWebInterfaceBrowserCookieManager> GetCookieManager() const override + { + return DefaultCookieManager; + } + + virtual TSharedPtr<IWebInterfaceBrowserCookieManager> GetCookieManager(TOptional<FString> ContextId) const override; + + virtual bool RegisterContext(const FInterfaceBrowserContextSettings& Settings) override; + + virtual bool UnregisterContext(const FString& ContextId) override; + + virtual bool RegisterSchemeHandlerFactory(FString Scheme, FString Domain, IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory) override; + + virtual bool UnregisterSchemeHandlerFactory(IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory) override; + + virtual bool IsDevToolsShortcutEnabled() override + { + return bDevToolsShortcutEnabled; + } + + virtual void SetDevToolsShortcutEnabled(bool Value) override + { + bDevToolsShortcutEnabled = Value; + } + + virtual void SetJSBindingToLoweringEnabled(bool bEnabled) override + { + bJSBindingsToLoweringEnabled = bEnabled; + } + + virtual void ClearOldCacheFolders(const FString& CachePathRoot, const FString& CachePrefix) override; + + /** Set a reference to UWebBrowser's default material*/ + virtual void SetDefaultMaterial(UMaterialInterface* InDefaultMaterial) override + { + DefaultMaterial = InDefaultMaterial; + } + + /** Set a reference to UWebBrowser's translucent material*/ + virtual void SetDefaultTranslucentMaterial(UMaterialInterface* InDefaultMaterial) override + { + DefaultTranslucentMaterial = InDefaultMaterial; + } + + /** Get a reference to UWebBrowser's default material*/ + virtual UMaterialInterface* GetDefaultMaterial() override + { + return DefaultMaterial; + } + + /** Get a reference to UWebBrowser's translucent material*/ + virtual UMaterialInterface* GetDefaultTranslucentMaterial() override + { + return DefaultTranslucentMaterial; + } + +public: + + // FTSTickerObjectBase Interface + + virtual bool Tick(float DeltaTime) override; + +#if WITH_CEF3 + /** Return true if this URL will support adding an Authorization header to it */ + bool URLRequestAllowsCredentials(const FString& URL); +#endif +private: + + TSharedPtr<IWebInterfaceBrowserCookieManager> DefaultCookieManager; + +#if WITH_CEF3 + /** When new render processes are created, send all permanent variable bindings to them. */ + void HandleRenderProcessCreated(CefRefPtr<CefListValue> ExtraInfo); + /** Helper function to generate the CEF build unique name for the cache_path */ + FString GenerateWebCacheFolderName(const FString &InputPath); + /** Helper function that blocks until the CEF task queue has processed a posted task, flushing the queue */ + void WaitForTaskQueueFlush(); + + /** Pointer to the CEF App implementation */ + CefRefPtr<FCEFInterfaceBrowserApp> CEFBrowserApp; + + TMap<FString, CefRefPtr<CefRequestContext>> RequestContexts; + TMap<FString, CefRefPtr<FCEFInterfaceResourceContextHandler>> RequestResourceHandlers; + FCefInterfaceSchemeHandlerFactories SchemeHandlerFactories; + bool bAllowCEF; + bool bTaskFinished; +#endif + + /** List of currently existing browser windows */ +#if WITH_CEF3 + TArray<TWeakPtr<FCEFWebInterfaceBrowserWindow>> WindowInterfaces; +#endif + + /** Critical section for thread safe modification of WindowInterfaces array. */ + FCriticalSection WindowInterfacesCS; + + TSharedRef<IWebInterfaceBrowserWindowFactory> WebBrowserWindowFactory; + + bool bDevToolsShortcutEnabled; + + bool bJSBindingsToLoweringEnabled; + + bool bAppIsFocused; + +#if WITH_CEF3 + /** Did CEF successfully initialize itself */ + bool bCEFInitialized; +#endif + + /** Reference to UWebBrowser's default material*/ + UMaterialInterface* DefaultMaterial; + + /** Reference to UWebBrowser's translucent material*/ + UMaterialInterface* DefaultTranslucentMaterial; + +}; + +PRAGMA_ENABLE_DEPRECATION_WARNINGS + +#if WITH_CEF3 + +class CefCookieManager; + +class FCefWebInterfaceCookieManagerFactory +{ +public: + static TSharedRef<IWebInterfaceBrowserCookieManager> Create( + const CefRefPtr<CefCookieManager>& CookieManager); +}; + +#endif diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserViewport.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserViewport.cpp new file mode 100644 index 0000000000000000000000000000000000000000..3dda56321f9c10fea3da7df7ddf6a0478aa3fd6e --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceBrowserViewport.cpp @@ -0,0 +1,133 @@ +// Engine/Source/Runtime/WebBrowser/Private/WebBrowserViewport.cpp + +#include "WebInterfaceBrowserViewport.h" +#include "Textures/SlateShaderResource.h" +#include "Widgets/SWidget.h" +#include "IWebInterfaceBrowserWindow.h" +#include "Layout/WidgetPath.h" + +#if WITH_CEF3 +#include "CEF/CEFWebInterfaceBrowserWindow.h" +#endif + +FIntPoint FWebInterfaceBrowserViewport::GetSize() const +{ + return (WebBrowserWindow->GetTexture(bIsPopup) != nullptr) + ? FIntPoint(WebBrowserWindow->GetTexture(bIsPopup)->GetWidth(), WebBrowserWindow->GetTexture(bIsPopup)->GetHeight()) + : FIntPoint(); +} + +FSlateShaderResource* FWebInterfaceBrowserViewport::GetViewportRenderTargetTexture() const +{ + return WebBrowserWindow->GetTexture(bIsPopup); +} + +void FWebInterfaceBrowserViewport::Tick( const FGeometry& AllottedGeometry, double InCurrentTime, float DeltaTime ) +{ + if (!bIsPopup) + { + const float DPI = (WebBrowserWindow->GetParentWindow().IsValid() ? WebBrowserWindow->GetParentWindow()->GetNativeWindow()->GetDPIScaleFactor() : 1.0f); + const float DPIScale = AllottedGeometry.Scale / DPI; + FVector2D AbsoluteSize = AllottedGeometry.GetLocalSize() * DPIScale; + WebBrowserWindow->SetViewportSize(AbsoluteSize.IntPoint(), AllottedGeometry.GetAbsolutePosition().IntPoint()); + +#if WITH_CEF3 + // Forward the AllottedGeometry to the WebBrowserWindow so the IME implementation can use it + TSharedPtr<FCEFWebInterfaceBrowserWindow> CefWebBrowserWindow = StaticCastSharedPtr<FCEFWebInterfaceBrowserWindow>(WebBrowserWindow); + CefWebBrowserWindow->UpdateCachedGeometry(AllottedGeometry); +#endif + } +} + +bool FWebInterfaceBrowserViewport::RequiresVsync() const +{ + return false; +} + +FCursorReply FWebInterfaceBrowserViewport::OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) +{ + return WebBrowserWindow->OnCursorQuery(MyGeometry, CursorEvent); +} + +FReply FWebInterfaceBrowserViewport::OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + // Capture mouse on left button down so that you can drag out of the viewport + FReply Reply = WebBrowserWindow->OnMouseButtonDown(MyGeometry, MouseEvent, bIsPopup); + if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) + { + const FWidgetPath* Path = MouseEvent.GetEventPath(); + if (Path->IsValid()) + { + TSharedRef<SWidget> TopWidget = Path->Widgets.Last().Widget; + return Reply.CaptureMouse(TopWidget); + } + } + return Reply; +} + +FReply FWebInterfaceBrowserViewport::OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + // Release mouse capture when left button released + FReply Reply = WebBrowserWindow->OnMouseButtonUp(MyGeometry, MouseEvent, bIsPopup); + if (MouseEvent.GetEffectingButton() == EKeys::LeftMouseButton) + { + return Reply.ReleaseMouseCapture(); + } + return Reply; +} + +void FWebInterfaceBrowserViewport::OnMouseEnter(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ +} + +void FWebInterfaceBrowserViewport::OnMouseLeave(const FPointerEvent& MouseEvent) +{ + WebBrowserWindow->OnMouseLeave(MouseEvent); +} + +FReply FWebInterfaceBrowserViewport::OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + return WebBrowserWindow->OnMouseMove(MyGeometry, MouseEvent, bIsPopup); +} + +FReply FWebInterfaceBrowserViewport::OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) +{ + return WebBrowserWindow->OnMouseWheel(MyGeometry, MouseEvent, bIsPopup); +} + +FReply FWebInterfaceBrowserViewport::OnTouchGesture(const FGeometry& MyGeometry, const FPointerEvent& GestureEvent) +{ + return WebBrowserWindow->OnTouchGesture(MyGeometry, GestureEvent, bIsPopup); +} + +FReply FWebInterfaceBrowserViewport::OnMouseButtonDoubleClick(const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent) +{ + FReply Reply = WebBrowserWindow->OnMouseButtonDoubleClick(InMyGeometry, InMouseEvent, bIsPopup); + return Reply; +} + +FReply FWebInterfaceBrowserViewport::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + return WebBrowserWindow->OnKeyDown(InKeyEvent) ? FReply::Handled() : FReply::Unhandled(); +} + +FReply FWebInterfaceBrowserViewport::OnKeyUp(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) +{ + return WebBrowserWindow->OnKeyUp(InKeyEvent) ? FReply::Handled() : FReply::Unhandled(); +} + +FReply FWebInterfaceBrowserViewport::OnKeyChar( const FGeometry& MyGeometry, const FCharacterEvent& InCharacterEvent ) +{ + return WebBrowserWindow->OnKeyChar(InCharacterEvent) ? FReply::Handled() : FReply::Unhandled(); +} + +FReply FWebInterfaceBrowserViewport::OnFocusReceived(const FFocusEvent& InFocusEvent) +{ + WebBrowserWindow->OnFocus(true, bIsPopup); + return FReply::Handled(); +} + +void FWebInterfaceBrowserViewport::OnFocusLost(const FFocusEvent& InFocusEvent) +{ + WebBrowserWindow->OnFocus(false, bIsPopup); +} diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceJSFunction.cpp b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceJSFunction.cpp new file mode 100644 index 0000000000000000000000000000000000000000..cbedea67a9c9abbdebb0dc82d598082d17a289db --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceJSFunction.cpp @@ -0,0 +1,136 @@ +// Engine/Source/Runtime/WebBrowser/Private/WebJSFunction.cpp + +#include "WebInterfaceJSFunction.h" +#include "WebInterfaceJSScripting.h" + +#include UE_INLINE_GENERATED_CPP_BY_NAME(WebInterfaceJSFunction) + +#if WITH_CEF3 +#if PLATFORM_WINDOWS +#include "Windows/WindowsHWrapper.h" +#include "Windows/AllowWindowsPlatformTypes.h" +#include "Windows/AllowWindowsPlatformAtomics.h" +#endif +#pragma push_macro("OVERRIDE") +#undef OVERRIDE // cef headers provide their own OVERRIDE macro +THIRD_PARTY_INCLUDES_START +#if PLATFORM_APPLE +PRAGMA_DISABLE_DEPRECATION_WARNINGS +#endif +#include "include/cef_values.h" +#if PLATFORM_APPLE +PRAGMA_ENABLE_DEPRECATION_WARNINGS +#endif +THIRD_PARTY_INCLUDES_END +#pragma pop_macro("OVERRIDE") +#if PLATFORM_WINDOWS +#include "Windows/HideWindowsPlatformAtomics.h" +#include "Windows/HideWindowsPlatformTypes.h" +#endif +#endif + +FWebInterfaceJSParam::~FWebInterfaceJSParam() +{ + // Since the FString, StructWrapper, TArray, and TMap members are in a union, they may or may not be valid, so we have to call the destructors manually. + switch (Tag) + { + case PTYPE_STRING: + delete StringValue; + break; + case PTYPE_STRUCT: + delete StructValue; + break; + case PTYPE_ARRAY: + delete ArrayValue; + break; + case PTYPE_MAP: + delete MapValue; + break; + default: + break; + } +} + +FWebInterfaceJSParam::FWebInterfaceJSParam(const FWebInterfaceJSParam& Other) + : Tag(Other.Tag) +{ + switch (Other.Tag) + { + case PTYPE_BOOL: + BoolValue = Other.BoolValue; + break; + case PTYPE_DOUBLE: + DoubleValue = Other.DoubleValue; + break; + case PTYPE_INT: + IntValue = Other.IntValue; + break; + case PTYPE_STRING: + StringValue = new FString(*Other.StringValue); + break; + case PTYPE_NULL: + break; + case PTYPE_OBJECT: + ObjectValue = Other.ObjectValue; + break; + case PTYPE_STRUCT: + StructValue = Other.StructValue->Clone(); + break; + case PTYPE_ARRAY: + ArrayValue = new TArray<FWebInterfaceJSParam>(*Other.ArrayValue); + break; + case PTYPE_MAP: + MapValue = new TMap<FString, FWebInterfaceJSParam>(*Other.MapValue); + break; + } +} + +FWebInterfaceJSParam::FWebInterfaceJSParam(FWebInterfaceJSParam&& Other) + : Tag(Other.Tag) +{ + switch (Other.Tag) + { + case PTYPE_BOOL: + BoolValue = Other.BoolValue; + break; + case PTYPE_DOUBLE: + DoubleValue = Other.DoubleValue; + break; + case PTYPE_INT: + IntValue = Other.IntValue; + break; + case PTYPE_STRING: + StringValue = Other.StringValue; + Other.StringValue = nullptr; + break; + case PTYPE_NULL: + break; + case PTYPE_OBJECT: + ObjectValue = Other.ObjectValue; + Other.ObjectValue = nullptr; + break; + case PTYPE_STRUCT: + StructValue = Other.StructValue; + Other.StructValue = nullptr; + break; + case PTYPE_ARRAY: + ArrayValue = Other.ArrayValue; + Other.ArrayValue = nullptr; + break; + case PTYPE_MAP: + MapValue = Other.MapValue; + Other.MapValue = nullptr; + break; + } + + Other.Tag = PTYPE_NULL; +} + +void FWebInterfaceJSCallbackBase::Invoke(int32 ArgCount, FWebInterfaceJSParam Arguments[], bool bIsError) const +{ + TSharedPtr<FWebInterfaceJSScripting> Scripting = ScriptingPtr.Pin(); + if (Scripting.IsValid()) + { + Scripting->InvokeJSFunction(CallbackId, ArgCount, Arguments, bIsError); + } +} diff --git a/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceJSScripting.h b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceJSScripting.h new file mode 100644 index 0000000000000000000000000000000000000000..3555d2cc69dd7facf0fd0d934f96894a453bb3d5 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Private/WebInterfaceJSScripting.h @@ -0,0 +1,149 @@ +// Engine/Source/Runtime/WebBrowser/Private/WebJSScripting.h + +#pragma once + +#include "CoreMinimal.h" +#include "Misc/Guid.h" +#include "WebInterfaceJSFunction.h" +#include "UObject/GCObject.h" + +class Error; + +/** + * Implements handling of bridging UObjects client side with JavaScript renderer side. + */ +class FWebInterfaceJSScripting + : public FGCObject +{ +public: + FWebInterfaceJSScripting(bool bInJSBindingToLoweringEnabled) + : BaseGuid(FGuid::NewGuid()) + , bJSBindingToLoweringEnabled(bInJSBindingToLoweringEnabled) + {} + + virtual void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) =0; + virtual void UnbindUObject(const FString& Name, UObject* Object = nullptr, bool bIsPermanent = true) =0; + + + virtual void InvokeJSFunction(FGuid FunctionId, int32 ArgCount, FWebInterfaceJSParam Arguments[], bool bIsError=false) =0; + virtual void InvokeJSErrorResult(FGuid FunctionId, const FString& Error) =0; + + FString GetBindingName(const FString& Name, UObject* Object) const + { + return bJSBindingToLoweringEnabled ? Name.ToLower() : Name; + } + + FString GetBindingName(const FFieldVariant& Property) const + { + return bJSBindingToLoweringEnabled ? Property.GetName().ToLower() : Property.GetName(); + } + +public: + + // FGCObject API + virtual void AddReferencedObjects( FReferenceCollector& Collector ) override + { + // Ensure bound UObjects are not garbage collected as long as this object is valid. + for (auto& Binding : BoundObjects) + { + Collector.AddReferencedObject(Binding.Key); + } + } + virtual FString GetReferencerName() const override + { + return TEXT("FWebInterfaceJSScripting"); + } + +protected: + // Creates a reversible memory addres -> psuedo-guid mapping. + // This is done by xoring the address with the first 64 bits of a base guid owned by the instance. + // Used to identify UObjects from the render process withough exposing internal pointers. + FGuid PtrToGuid(UObject* Ptr) + { + FGuid Guid = BaseGuid; + if (Ptr == nullptr) + { + Guid.Invalidate(); + } + else + { + UPTRINT IntPtr = reinterpret_cast<UPTRINT>(Ptr); + if (sizeof(UPTRINT) > 4) + { + Guid[0] ^= (static_cast<uint64>(IntPtr) >> 32); + } + Guid[1] ^= IntPtr & 0xFFFFFFFF; + } + return Guid; + } + + // In addition to reversing the mapping, it verifies that we are currently holding on to an instance of that UObject + UObject* GuidToPtr(const FGuid& Guid) + { + UPTRINT IntPtr = 0; + if (sizeof(UPTRINT) > 4) + { + IntPtr = static_cast<UPTRINT>(static_cast<uint64>(Guid[0] ^ BaseGuid[0]) << 32); + } + IntPtr |= (Guid[1] ^ BaseGuid[1]) & 0xFFFFFFFF; + + UObject* Result = reinterpret_cast<UObject*>(IntPtr); + if (BoundObjects.Contains(Result)) + { + return Result; + } + else + { + return nullptr; + } + } + + void RetainBinding(UObject* Object) + { + if (BoundObjects.Contains(Object)) + { + if(!BoundObjects[Object].bIsPermanent) + { + BoundObjects[Object].Refcount++; + } + } + else + { + BoundObjects.Add(Object, {false, 1}); + } + } + + void ReleaseBinding(UObject* Object) + { + if (BoundObjects.Contains(Object)) + { + auto& Binding = BoundObjects[Object]; + if(!Binding.bIsPermanent) + { + Binding.Refcount--; + if (Binding.Refcount <= 0) + { + BoundObjects.Remove(Object); + } + } + } + } + + struct ObjectBinding + { + bool bIsPermanent; + int32 Refcount; + }; + + /** Private data */ + FGuid BaseGuid; + + /** UObjects currently visible on the renderer side. */ + TMap<UObject*, ObjectBinding> BoundObjects; + + /** Reverse lookup for permanent bindings */ + TMap<FString, UObject*> PermanentUObjectsByName; + + /** The to-lowering option enable for the binding names. */ + const bool bJSBindingToLoweringEnabled; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserAdapter.h b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserAdapter.h new file mode 100644 index 0000000000000000000000000000000000000000..67f075e756f014b08f123d0d0d68f20237774f88 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserAdapter.h @@ -0,0 +1,28 @@ +// Engine/Source/Runtime/WebBrowser/Public/IWebBrowserAdapter.h +#pragma once + +#include "CoreMinimal.h" +#include "IWebInterfaceBrowserWindow.h" + +class IWebInterfaceBrowserAdapter +{ +public: + + virtual FString GetName() const = 0; + + virtual bool IsPermanent() const = 0; + + virtual void ConnectTo(const TSharedRef<IWebInterfaceBrowserWindow>& BrowserWindow) = 0; + + virtual void DisconnectFrom(const TSharedRef<IWebInterfaceBrowserWindow>& BrowserWindow) = 0; + +}; + +class WEBBROWSERUI_API FWebInterfaceBrowserAdapterFactory +{ +public: + + static TSharedRef<IWebInterfaceBrowserAdapter> Create(const FString& Name, UObject* JSBridge, bool IsPermanent); + + static TSharedRef<IWebInterfaceBrowserAdapter> Create(const FString& Name, UObject* JSBridge, bool IsPermanent, const FString& ConnectScriptText, const FString& DisconnectScriptText); +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserCookieManager.h b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserCookieManager.h new file mode 100644 index 0000000000000000000000000000000000000000..f9ab25f23f724f8a86315596bfec4ab02597c455 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserCookieManager.h @@ -0,0 +1,68 @@ +// Engine/Source/Runtime/WebBrowser/Public/IWebBrowserCookieManager.h +#pragma once + +#include "CoreMinimal.h" + +struct FWebInterfaceBrowserCookie +{ +public: + // The cookie name. + FString Name; + + // The cookie value. + FString Value; + + // If is empty a host cookie will be created instead of a domain + // cookie. Domain cookies are stored with a leading "." and are visible to + // sub-domains whereas host cookies are not. + FString Domain; + + // If is non-empty only URLs at or below the path will get the cookie + // value. + FString Path; + + // If true the cookie will only be sent for HTTPS requests. + bool bSecure; + + // If true the cookie will only be sent for HTTP requests. + bool bHttpOnly; + + // If true the cookie will expire at the specified Expires datetime. + bool bHasExpires; + + // The cookie expiration date is only valid if bHasExpires is true. + FDateTime Expires; +}; + +class IWebInterfaceBrowserCookieManager +{ +public: + typedef struct FWebInterfaceBrowserCookie FCookie; +public: + + /** + * Sets a cookie given a valid URL. + * + * This function expects each attribute to be well-formed. It will + * check for disallowed characters (e.g. the ';' character is disallowed + * within the cookie Value field) and fail without setting the cookie if + * such characters are found. + * + * @param URL The base URL to match when searching for cookies to remove. Use blank to match all URLs. + * @param Cookie The struct defining the state of the cookie to set + * @param Completed A callback function that will be invoked asynchronously on the game thread when the set is complete passing success bool. + */ + virtual void SetCookie(const FString& URL, const FCookie& Cookie, TFunction<void(bool)> Completed = nullptr) = 0; + + /** + * Delete all browser cookies. + * + * Removes all matching cookies. Leave both URL and CookieName blank to delete the entire cookie database. + * The actual deletion will be scheduled on the browser IO thread, so the operation may not have completed when the function returns. + * + * @param URL The base URL to match when searching for cookies to remove. Use blank to match all URLs. + * @param CookieName The name of the cookie to delete. If left unspecified, all cookies will be removed. + * @param Completed A callback function that will be invoked asynchronously on the game thread when the deletion is complete passing in the number of deleted objects. + */ + virtual void DeleteCookies(const FString& URL = TEXT(""), const FString& CookieName = TEXT(""), TFunction<void(int)> Completed = nullptr) = 0; +}; \ No newline at end of file diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserDialog.h b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserDialog.h new file mode 100644 index 0000000000000000000000000000000000000000..d6afec210aec618d790928f06772b11fbee4931a --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserDialog.h @@ -0,0 +1,82 @@ +// Engine/Source/Runtime/WebBrowser/Public/IWebBrowserDialog.h + +#pragma once + +#include "CoreMinimal.h" + +/** + * Type of browser dialog to show. + */ + +enum class EWebInterfaceBrowserDialogType +{ + /** Alert dialog. Used to show a message to a user. It should only have a single button to dismiss it. */ + Alert = 0, + /** Confirm dialog. Shows a message to the user. It should have two buttons to either accept or decline the prompt. */ + Confirm, + /** Prompt dialog. Shows a prompt asking for user input. The user can enter text and either confirm or dismiss it. */ + Prompt, + /** Unload dialog. This type of dialog is shown to confirm navigating away from a page containing user-edited content. */ + Unload = 127, +}; + +/** + * Return value from dialog event handle specifying what action should be taken. + */ +enum class EWebInterfaceBrowserDialogEventResponse +{ + /** Return Unhandled to use the default dialog implementation. This is the default behavior when no handler is attached. */ + Unhandled, + /** Do not show any dialog and return as if the user accepted the action. */ + Continue, + /** Do not show any dialog and return as if the user dismissed the action. */ + Ignore, + /** The event handler will take care of showing the dialog. It must call IWebBrowserDialog::Continue once it has been dismissed. */ + Handled, +}; + +/** + * Browser dialog parameters passed to OnBrowserDialog event handlers. + */ +class IWebInterfaceBrowserDialog +{ +public: + + virtual ~IWebInterfaceBrowserDialog() + {} + + /** + * What kind of dialog should be shown. + * @return the dialog type + */ + virtual EWebInterfaceBrowserDialogType GetType() = 0; + + /** + * Tell the browser to continue with the result of the dialog. + * If this method is used, the original event handler must return EWebBrowserDialogEventResponse::Handled. + * + * @param Success Did the user accept the dialog or not? + * @param UserResponse Only for Prompt dialog, the text entered by the user. + */ + virtual void Continue(bool Success = true, const FText& UserResponse = FText::GetEmpty()) = 0; + + /** + * Get the dialog message. + * @return the message to show in the dialog. + */ + virtual const FText& GetMessageText() = 0; + + /** + * Only valid for Prompt dialogs. + * @return the default value to show in the text entry box. + */ + virtual const FText& GetDefaultPrompt() = 0; + + /** + * Only valid for Unload dialogs. + * @return true if the dialog is confirming a reload of the current page. + */ + virtual bool IsReload() = 0; + + +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserPopupFeatures.h b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserPopupFeatures.h new file mode 100644 index 0000000000000000000000000000000000000000..69b7a10b96245dc16545c8971e95df62940ff028 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserPopupFeatures.h @@ -0,0 +1,29 @@ +// Engine/Source/Runtime/WebBrowser/Public/IWebBrowserPopupFeatures.h + +#pragma once + +#include "CoreMinimal.h" + +class IWebInterfaceBrowserPopupFeatures +{ +public: + virtual ~IWebInterfaceBrowserPopupFeatures() {}; + + virtual int GetX() const = 0; + virtual bool IsXSet() const = 0; + virtual int GetY() const = 0; + virtual bool IsYSet() const = 0; + virtual int GetWidth() const = 0; + virtual bool IsWidthSet() const = 0; + virtual int GetHeight() const = 0; + virtual bool IsHeightSet() const = 0; + virtual bool IsMenuBarVisible() const = 0; + virtual bool IsStatusBarVisible() const = 0; + virtual bool IsToolBarVisible() const = 0; + virtual bool IsLocationBarVisible() const = 0; + virtual bool IsScrollbarsVisible() const = 0; + virtual bool IsResizable() const = 0; + virtual bool IsFullscreen() const = 0; + virtual bool IsDialog() const = 0; + virtual TArray<FString> GetAdditionalFeatures() const = 0; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserResourceLoader.h b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserResourceLoader.h new file mode 100644 index 0000000000000000000000000000000000000000..dcde00fc24ef6f08f568bf5d168259d65f2339b1 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserResourceLoader.h @@ -0,0 +1,8 @@ +// Engine/Source/Runtime/WebBrowser/Public/IWebInterfaceBrowserResourceLoader.h + +#pragma once + +#include "CoreMinimal.h" + +typedef TMap<FString, FString> FInterfaceContextRequestHeaders; +DECLARE_DELEGATE_FourParams(FOnBeforeInterfaceContextResourceLoadDelegate, FString /*Url*/, FString /*ResourceType*/, FInterfaceContextRequestHeaders& /*AdditionalHeaders*/, const bool /*AllowUserCredentials*/); diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserSchemeHandler.h b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserSchemeHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..fa2f32165f6a0c62a921860b65a695e84ed6457f --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserSchemeHandler.h @@ -0,0 +1,102 @@ +// Engine/Source/Runtime/WebBrowser/Public/IWebBrowserSchemeHandler.h +#pragma once + +#include "CoreMinimal.h" + +/** + * This is the interface that needs to be implemented to handle a request made via a custom scheme. + * It will be created by implementing an IWebBrowserSchemeHandlerFactory, given to the web browser singleton. + */ +class WEBBROWSERUI_API IWebInterfaceBrowserSchemeHandler +{ +public: + /** + * An interface for setting response headers emulating a http implementation. + */ + class IHeaders + { + public: + /** + * Sets the mime type for the response. + * @param MimeType The Mime Type. + */ + virtual void SetMimeType(const TCHAR* MimeType) = 0; + + /** + * Sets the status code for the response. + * @param StatusCode The status code. + */ + virtual void SetStatusCode(int32 StatusCode) = 0; + + /** + * Sets the content length for the response. + * @param ContentLength The size of the response content in bytes. + */ + virtual void SetContentLength(int32 ContentLength) = 0; + + /** + * Sets a redirect url for the response. Other calls will be ignored if this is used. + * @param Url The url to redirect to. + */ + virtual void SetRedirect(const TCHAR* Url) = 0; + + /** + * Sets a header for the response. + * @param Key The header key. + * @param Value The header value. + */ + virtual void SetHeader(const TCHAR* Key, const TCHAR* Value) = 0; + }; + +public: + virtual ~IWebInterfaceBrowserSchemeHandler() {} + + /** + * Process an incoming request. + * @param Verb This is the verb used for the request (GET, PUT, POST, etc). + * @param Url This is the full url for the request being made. + * @param OnHeadersReady You must execute this delegate once the response headers are ready to be retrieved with GetResponseHeaders. + * You may execute it during this call to state headers are available now. + * @return You should return true if the request has been accepted and will be processed, otherwise false to cancel this request. + */ + virtual bool ProcessRequest(const FString& Verb, const FString& Url, const FSimpleDelegate& OnHeadersReady) = 0; + + /** + * Retrieves the headers for this request. + * @param OutHeaders The interface to use to set headers. + */ + virtual void GetResponseHeaders(IHeaders& OutHeaders) = 0; + + /** + * Retrieves the headers for this request. + * @param OutBytes You should copy up to BytesToRead of data to this ptr. + * @param BytesToRead The maximum number of bytes that can be copied to OutBytes. + * @param BytesRead You should set this to the number of bytes that were copied. + * This can be set to zero, to indicate more data is not ready yet, and OnMoreDataReady must then be + * executed when there is. + * @param OnMoreDataReady You should execute this delegate when more data is available to read. + * @return You should return true if more data needs to be read, otherwise false if this is the end of the response data. + */ + virtual bool ReadResponse(uint8* OutBytes, int32 BytesToRead, int32& BytesRead, const FSimpleDelegate& OnMoreDataReady) = 0; + + /** + * Called if the request should be canceled. + */ + virtual void Cancel() = 0; +}; + +/** + * This is the interface that needs to be implemented to instantiate a scheme request handler. + */ +class WEBBROWSERUI_API IWebInterfaceBrowserSchemeHandlerFactory +{ +public: + virtual ~IWebInterfaceBrowserSchemeHandlerFactory() {} + + /** + * Instantiates an appropriate handler for the given request details. + * @param Verb This is the verb used for the request (GET, PUT, POST, etc). + * @param Url This is the full url for the request being made. + */ + virtual TUniquePtr<IWebInterfaceBrowserSchemeHandler> Create(FString Verb, FString Url) = 0; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserSingleton.h b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserSingleton.h new file mode 100644 index 0000000000000000000000000000000000000000..588b16912fceda225efeab58512060f33d755d52 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserSingleton.h @@ -0,0 +1,208 @@ +// Engine/Source/Runtime/WebBrowser/Public/IWebBrowserSingleton.h + +#pragma once + +#include "CoreMinimal.h" +#include "Rendering/SlateRenderer.h" +#include "IWebInterfaceBrowserResourceLoader.h" + +class FCEFWebInterfaceBrowserWindow; +class IWebInterfaceBrowserCookieManager; +class IWebInterfaceBrowserWindow; +class IWebInterfaceBrowserSchemeHandlerFactory; +class UMaterialInterface; +struct FWebInterfaceBrowserWindowInfo; + +class IWebInterfaceBrowserWindowFactory +{ +public: + + virtual TSharedPtr<IWebInterfaceBrowserWindow> Create( + TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent, + TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo) = 0; + + virtual TSharedPtr<IWebInterfaceBrowserWindow> Create( + void* OSWindowHandle, + FString InitialURL, + bool bUseTransparency, + bool bThumbMouseButtonNavigation, + bool InterceptLoadRequests = false, + TOptional<FString> ContentsToLoad = TOptional<FString>(), + bool ShowErrorMessage = true, + FColor BackgroundColor = FColor(255, 255, 255, 255)) = 0; +}; + +struct WEBBROWSERUI_API FInterfaceBrowserContextSettings +{ + FInterfaceBrowserContextSettings(const FString& InId) + : Id(InId) + , AcceptLanguageList() + , CookieStorageLocation() + , bPersistSessionCookies(false) + , bIgnoreCertificateErrors(false) + , bEnableNetSecurityExpiration(true) + { } + + FString Id; + FString AcceptLanguageList; + FString CookieStorageLocation; + bool bPersistSessionCookies; + bool bIgnoreCertificateErrors; + bool bEnableNetSecurityExpiration; + FOnBeforeInterfaceContextResourceLoadDelegate OnBeforeContextResourceLoad; +}; + + +struct WEBBROWSERUI_API FCreateInterfaceBrowserWindowSettings +{ + + FCreateInterfaceBrowserWindowSettings() + : OSWindowHandle(nullptr) + , InitialURL() + , bAcceleratedPaint(false) + , bUseTransparency(false) + , bInterceptLoadRequests(false) + , bUseNativeCursors(false) + , bThumbMouseButtonNavigation(false) + , ContentsToLoad() + , bShowErrorMessage(true) + , BackgroundColor(FColor(255, 255, 255, 255)) + , BrowserFrameRate(60) + , Context() + , AltRetryDomains() + { } + + void* OSWindowHandle; + FString InitialURL; + bool bAcceleratedPaint; + bool bUseTransparency; + bool bInterceptLoadRequests; + bool bUseNativeCursors; + bool bThumbMouseButtonNavigation; + TOptional<FString> ContentsToLoad; + bool bShowErrorMessage; + FColor BackgroundColor; + int BrowserFrameRate; + TOptional<FInterfaceBrowserContextSettings> Context; + TArray<FString> AltRetryDomains; +}; + +/** + * A singleton class that takes care of general web browser tasks + */ +class WEBBROWSERUI_API IWebInterfaceBrowserSingleton +{ +public: + /** + * Virtual Destructor + */ + virtual ~IWebInterfaceBrowserSingleton() {}; + + /** @return A factory object that can be used to construct additional WebBrowserWindows on demand. */ + virtual TSharedRef<IWebInterfaceBrowserWindowFactory> GetWebBrowserWindowFactory() const = 0; + + + /** + * Create a new web browser window + * + * @param BrowserWindowParent The parent browser window + * @param BrowserWindowInfo Info for setting up the new browser window + * @return New Web Browser Window Interface (may be null if not supported) + */ + virtual TSharedPtr<IWebInterfaceBrowserWindow> CreateBrowserWindow( + TSharedPtr<FCEFWebInterfaceBrowserWindow>& BrowserWindowParent, + TSharedPtr<FWebInterfaceBrowserWindowInfo>& BrowserWindowInfo + ) = 0; + + /** + * Create a new web browser window + * + * @param Settings Struct containing browser window settings + * @return New Web Browser Window Interface (may be null if not supported) + */ + virtual TSharedPtr<IWebInterfaceBrowserWindow> CreateBrowserWindow(const FCreateInterfaceBrowserWindowSettings& Settings) = 0; + +#if BUILD_EMBEDDED_APP + virtual TSharedPtr<IWebInterfaceBrowserWindow> CreateNativeBrowserProxy() = 0; +#endif + + virtual TSharedPtr<class IWebInterfaceBrowserCookieManager> GetCookieManager() const = 0; + + virtual TSharedPtr<class IWebInterfaceBrowserCookieManager> GetCookieManager(TOptional<FString> ContextId) const = 0; + + virtual bool RegisterContext(const FInterfaceBrowserContextSettings& Settings) = 0; + + virtual bool UnregisterContext(const FString& ContextId) = 0; + + // @return the application cache dir where the cookies are stored + virtual FString ApplicationCacheDir() const = 0; + /** + * Registers a custom scheme handler factory, for a given scheme and domain. The domain is ignored if the scheme is not a browser built in scheme + * and all requests will go through this factory. + * @param Scheme The scheme name to handle. + * @param Domain The domain name to handle. + * @param WebBrowserSchemeHandlerFactory The factory implementation for creating request handlers for this scheme. + */ + virtual bool RegisterSchemeHandlerFactory(FString Scheme, FString Domain, IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory) = 0; + + /** + * Unregister a custom scheme handler factory. The factory may still be used by existing open browser windows, but will no longer be provided for new ones. + * @param WebBrowserSchemeHandlerFactory The factory implementation to remove. + */ + virtual bool UnregisterSchemeHandlerFactory(IWebInterfaceBrowserSchemeHandlerFactory* WebBrowserSchemeHandlerFactory) = 0; + + /** + * Enable or disable CTRL/CMD-SHIFT-I shortcut to show the Chromium Dev tools window. + * The value defaults to true on debug builds, otherwise false. + * + * The relevant handlers for spawning new browser windows have to be set up correctly in addition to this flag being true before anything is shown. + * + * @param Value a boolean value to enable or disable the keyboard shortcut. + */ + virtual void SetDevToolsShortcutEnabled(bool Value) = 0; + + + /** + * Returns whether the CTRL/CMD-SHIFT-I shortcut to show the Chromium Dev tools window is enabled. + * + * The relevant handlers for spawning new browser windows have to be set up correctly in addition to this flag being true before anything is shown. + * + * @return a boolean value indicating whether the keyboard shortcut is enabled or not. + */ + virtual bool IsDevToolsShortcutEnabled() = 0; + + + /** + * Enable or disable to-lowering of JavaScript object member bindings. + * + * Due to how JavaScript to UObject bridges require the use of FNames for building up the JS API objects, it is possible for case-sensitivity issues + * to develop if an FName has been previously created with differing case to your function or property names. To-lowering the member names allows + * a guaranteed casing for the web page's JS to reference. + * + * Default behavior is enabled, so that all JS side objects have only lowercase members. + * + * @param bEnabled a boolean value to enable or disable the to-lowering. + */ + virtual void SetJSBindingToLoweringEnabled(bool bEnabled) = 0; + + + /** + * Delete old/unused web cache paths. Some Web implementations (i.e CEF) use version specific cache folders, this + * call lets you schedule a deletion of any now unused folders. Calling this may resulting in async disk I/O. + * + * @param CachePathRoot the base path used for saving the webcache folder + * @param CachePrefix the filename prefix we use for the cache folder (i.e "webcache") + */ + virtual void ClearOldCacheFolders(const FString &CachePathRoot, const FString &CachePrefix) = 0; + + + /** Set a reference to UWebBrowser's default material*/ + virtual void SetDefaultMaterial(UMaterialInterface* InDefaultMaterial) = 0; + /** Set a reference to UWebBrowser's translucent material*/ + virtual void SetDefaultTranslucentMaterial(UMaterialInterface* InDefaultMaterial) = 0; + + /** Get a reference to UWebBrowser's default material*/ + virtual UMaterialInterface* GetDefaultMaterial() = 0; + /** Get a reference to UWebBrowser's transparent material*/ + virtual UMaterialInterface* GetDefaultTranslucentMaterial() = 0; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserWindow.h b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserWindow.h new file mode 100644 index 0000000000000000000000000000000000000000..6fb3ef3c089b72ab5fe5f004d79474a38c370907 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/IWebInterfaceBrowserWindow.h @@ -0,0 +1,482 @@ +// Engine/Source/Runtime/WebBrowser/Public/IWebBrowserWindow.h + +#pragma once + +#include "CoreMinimal.h" +#include "Input/CursorReply.h" +#include "Input/Reply.h" +#include "Widgets/SWindow.h" +#include "SWebInterfaceBrowser.h" + +class Error; +class FSlateShaderResource; +class IWebInterfaceBrowserDialog; +class IWebInterfaceBrowserPopupFeatures; +enum class EWebInterfaceBrowserDialogEventResponse; + +enum class EWebInterfaceBrowserDocumentState +{ + Completed, + Error, + Loading, + NoDocument +}; + +enum class EWebInterfaceBrowserConsoleLogSeverity +{ + Default, + Verbose, + Debug, + Info, + Warning, + Error, + Fatal +}; + + +enum class EWebInterfaceTransitionSource +{ + Unknown, + /** Source is a link click or the JavaScript window.open function. */ + Link, + /** Source is some other "explicit" navigation action such as creating a new browser or using the LoadURL function. */ + Explicit, + /** Source is a subframe navigation. */ + AutoSubframe, + /** Source is a subframe navigation explicitly requested by the user. */ + ManualSubframe, + /** Source is a form submission by the user. */ + FormSubmit, + /** Source is a "reload" of the page via the Reload function */ + Reload +}; + +enum class EWebInterfaceTransitionSourceQualifier +{ + Unknown, + /** Attempted to visit a URL but was blocked. */ + Blocked, + /** Used the Forward or Back function to navigate among browsing history. */ + ForwardBack, + /** The beginning of a navigation chain. */ + ChainStart, + /** The last transition in a redirect chain. */ + ChainEnd, + /** Redirects caused by JavaScript or a meta refresh tag on the page. */ + ClientRedirect, + /** Used to test whether a transition involves a redirect. */ + ServerRedirect +}; + +struct FWebNavigationRequest +{ + bool bIsRedirect; + bool bIsMainFrame; + bool bIsExplicitTransition; + EWebInterfaceTransitionSource TransitionSource; + EWebInterfaceTransitionSourceQualifier TransitionSourceQualifier; +}; + +/** + * Interface for dealing with a Web Browser window + */ +class IWebInterfaceBrowserWindow +{ +public: + + /** + * Load the specified URL + * + * @param NewURL New URL to load + */ + virtual void LoadURL(FString NewURL) = 0; + + /** + * Load a string as data to create a web page + * + * @param Contents String to load + * @param DummyURL Dummy URL for the page + */ + virtual void LoadString(FString Contents, FString DummyURL) = 0; + + /** + * Set the desired size of the web browser viewport + * + * @param WindowSize Desired viewport size + */ + virtual void SetViewportSize(FIntPoint WindowSize, FIntPoint WindowPos = FIntPoint::NoneValue) = 0; + + /** + * Gets the current size of the web browser viewport if available, FIntPoint::NoneValue otherwise + * + * @param WindowSize Desired viewport size + */ + virtual FIntPoint GetViewportSize() const = 0; + + /** + * Gets interface to the texture representation of the browser + * + * @param bISpopup Whether to return the popup menu texture instead of the main browser window. + * @return A slate shader resource that can be rendered + */ + virtual FSlateShaderResource* GetTexture(bool bIsPopup = false) = 0; + + /** + * Checks whether the web browser is valid and ready for use + */ + virtual bool IsValid() const = 0; + + /** + * Checks whether the web browser has finished loaded the initial page. + */ + virtual bool IsInitialized() const = 0; + + /** + * Checks whether the web browser is currently being shut down + */ + virtual bool IsClosing() const = 0; + + /** Gets the loading state of the current document. */ + virtual EWebInterfaceBrowserDocumentState GetDocumentLoadingState() const = 0; + + /** + * Gets the current title of the Browser page + */ + virtual FString GetTitle() const = 0; + + /** + * Gets the currently loaded URL. + * + * @return The URL, or empty string if no document is loaded. + */ + virtual FString GetUrl() const = 0; + + /** + * Gets the source of the main frame as raw HTML. + * + * This method has to be called asynchronously by passing a callback function, which will be called at a later point when the + * result is ready. + * @param Callback A callable that takes a single string reference for handling the result. + */ + virtual void GetSource(TFunction<void (const FString&)> Callback) const = 0; + + /** + * Notify the browser that a key has been pressed + * + * @param InKeyEvent Key event + */ + virtual bool OnKeyDown(const FKeyEvent& InKeyEvent) = 0; + + /** + * Notify the browser that a key has been released + * + * @param InKeyEvent Key event + */ + virtual bool OnKeyUp(const FKeyEvent& InKeyEvent) = 0; + + /** + * Notify the browser of a character event + * + * @param InCharacterEvent Character event + */ + virtual bool OnKeyChar(const FCharacterEvent& InCharacterEvent) = 0; + + /** + * Notify the browser that a mouse button was pressed within it + * + * @param MyGeometry The Geometry of the browser + * @param MouseEvent Information about the input event + * @param bIsPopup True if the coordinates are relative to a popup menu window, otherwise false. + * + * @return FReply::Handled() if the mouse event was handled, FReply::Unhandled() oterwise + */ + virtual FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) = 0; + + /** + * Notify the browser that a mouse button was released within it + * + * @param MyGeometry The Geometry of the browser + * @param MouseEvent Information about the input event + * @param bIsPopup True if the coordinates are relative to a popup menu window, otherwise false. + * + * @return FReply::Handled() if the mouse event was handled, FReply::Unhandled() oterwise + */ + virtual FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) = 0; + + /** + * Notify the browser of a double click event + * + * @param MyGeometry The Geometry of the browser + * @param MouseEvent Information about the input event + * @param bIsPopup True if the coordinates are relative to a popup menu window, otherwise false. + * + * @return FReply::Handled() if the mouse event was handled, FReply::Unhandled() oterwise + */ + virtual FReply OnMouseButtonDoubleClick(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) = 0; + + /** + * Notify the browser that a mouse moved within it + * + * @param MyGeometry The Geometry of the browser + * @param MouseEvent Information about the input event + * @param bIsPopup True if the coordinates are relative to a popup menu window, otherwise false. + * + * @return FReply::Handled() if the mouse event was handled, FReply::Unhandled() oterwise + */ + virtual FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) = 0; + + /** + * Notify the browser that a mouse has left the window + * + * @param MouseEvent Information about the input event + */ + virtual void OnMouseLeave(const FPointerEvent& MouseEvent) = 0; + + /** + * Sets whether mouse wheel events should be handled by the window + */ + virtual void SetSupportsMouseWheel(bool bValue) = 0; + + /** + * Returns whether mouse wheel events should be handled by the window + */ + virtual bool GetSupportsMouseWheel() const = 0; + + /** + * Called when the mouse wheel is spun + * + * @param MyGeometry The Geometry of the browser + * @param MouseEvent Information about the input event + * @param bIsPopup True if the coordinates are relative to a popup menu window, otherwise false. + * + * @return FReply::Handled() if the mouse event was handled, FReply::Unhandled() oterwise + */ + virtual FReply OnMouseWheel(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent, bool bIsPopup) = 0; + + /** + * Called when a touch gesture is performed. + * + * @param MyGeometry The Geometry of the browser + * @param GestureEvent Information about the input event + * @param bIsPopup True if the coordinates are relative to a popup menu window, otherwise false. + * + * @return FReply::Handled() if the mouse event was handled, FReply::Unhandled() oterwise + */ + virtual FReply OnTouchGesture(const FGeometry& MyGeometry, const FPointerEvent& GestureEvent, bool bIsPopup) = 0; + + /** + * The system asks each widget under the mouse to provide a cursor. This event is bubbled. + * + * @return FCursorReply::Unhandled() if the event is not handled; return FCursorReply::Cursor() otherwise. + */ + virtual FCursorReply OnCursorQuery(const FGeometry& MyGeometry, const FPointerEvent& CursorEvent) = 0; + + /** + * Called when browser receives/loses focus + * @param SetFocus Whether the window gained or lost focus. + * @param bIsPopup True if the coordinates are relative to a popup menu window, otherwise false. + */ + virtual void OnFocus(bool SetFocus, bool bIsPopup) = 0; + + /** Called when Capture lost */ + virtual void OnCaptureLost() = 0; + + /** + * Returns true if the browser can navigate backwards. + */ + virtual bool CanGoBack() const = 0; + + /** Navigate backwards. */ + virtual void GoBack() = 0; + + /** + * Returns true if the browser can navigate forwards. + */ + virtual bool CanGoForward() const = 0; + + /** Navigate forwards. */ + virtual void GoForward() = 0; + + /** + * Returns true if the browser is currently loading. + */ + virtual bool IsLoading() const = 0; + + /** Reload the current page. */ + virtual void Reload() = 0; + + /** Stop loading the page. */ + virtual void StopLoad() = 0; + + /** Execute Javascript on the page. */ + virtual void ExecuteJavascript(const FString& Script) = 0; + + /** + * Close this window so that it can no longer be used. + * + * @param bForce Designates whether the web browser close should be forced. + * @param bBlockTillClosed Don't return until this browser object is fully closed. + */ + virtual void CloseBrowser(bool bForce, bool bBlockTillClosed = false) = 0; + + /** + * Expose a UObject instance to the browser runtime. + * Properties and Functions will be accessible from JavaScript side. + * As all communication with the rendering procesis asynchronous, return values (both for properties and function results) are wrapped into JS Future objects. + * + * @param Name The name of the object. The object will show up as window.ue.{Name} on the javascript side. If there is an existing object of the same name, this object will replace it. If bIsPermanent is false and there is an existing permanent binding, the permanent binding will be restored when the temporary one is removed. + * @param Object The object instance. + * @param bIsPermanent If true, the object will be visible to all pages loaded through this browser widget, otherwise, it will be deleted when navigating away from the current page. Non-permanent bindings should be registered from inside an OnLoadStarted event handler in order to be available before JS code starts loading. + */ + virtual void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) = 0; + + /** + * Remove an existing script binding registered by BindUObject. + * + * @param Name The name of the object to remove. + * @param Object The object will only be removed if it is the same object as the one passed in. + * @param bIsPermanent Must match the bIsPermanent argument passed to BindUObject. + */ + virtual void UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true) = 0; + + virtual void BindInputMethodSystem(ITextInputMethodSystem* TextInputMethodSystem) {} + + virtual void UnbindInputMethodSystem() {} + + /** + * Get current load error. + * + * @return an error code if the last page load resulted in an error, otherwise 0. + */ + virtual int GetLoadError() = 0; + + /** + * Disable or enable web view. + * + * @param bValue Setting this to true will prevent any updates from the background web browser. + */ + virtual void SetIsDisabled(bool bValue) = 0; + + /** + * Get parent SWindow for this window + */ + virtual TSharedPtr<class SWindow> GetParentWindow() const = 0; + + /** + * Set parent SWindow for this window + */ + virtual void SetParentWindow(TSharedPtr<class SWindow> Window) = 0; + + virtual void CheckTickActivity() {}; +public: + + /** A delegate that is invoked when the loading state of a document changed. */ + DECLARE_EVENT_OneParam(IWebInterfaceBrowserWindow, FOnDocumentStateChanged, EWebInterfaceBrowserDocumentState /*NewState*/); + virtual FOnDocumentStateChanged& OnDocumentStateChanged() = 0; + + /** A delegate to allow callbacks when a browser title changes. */ + DECLARE_EVENT_OneParam(IWebInterfaceBrowserWindow, FOnTitleChanged, FString /*NewTitle*/); + virtual FOnTitleChanged& OnTitleChanged() = 0; + + /** A delegate to allow callbacks when a frame url changes. */ + DECLARE_EVENT_OneParam(IWebInterfaceBrowserWindow, FOnUrlChanged, FString /*NewUrl*/); + virtual FOnUrlChanged& OnUrlChanged() = 0; + + /** A delegate to allow callbacks when a frame url changes. */ + DECLARE_EVENT_OneParam(IWebInterfaceBrowserWindow, FOnToolTip, FString /*ToolTipText*/); + virtual FOnToolTip& OnToolTip() = 0; + + /** A delegate that is invoked when the off-screen window has been repainted and requires an update. */ + DECLARE_EVENT(IWebInterfaceBrowserWindow, FOnNeedsRedraw) + virtual FOnNeedsRedraw& OnNeedsRedraw() = 0; + + /** A delegate that is invoked prior to browser navigation. */ + DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnBeforeBrowse, const FString& /*Url*/, const FWebNavigationRequest& /*Request*/) + virtual FOnBeforeBrowse& OnBeforeBrowse() = 0; + + /** A delegate that is invoked to allow user code to override the contents of a Url. */ + DECLARE_DELEGATE_RetVal_ThreeParams(bool, FOnLoadUrl, const FString& /*Method*/, const FString& /*Url*/, FString& /*OutBody*/) + virtual FOnLoadUrl& OnLoadUrl() = 0; + + /** A delegate that is invoked when a popup window is attempting to open. */ + DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnBeforePopupDelegate, FString, FString); + virtual FOnBeforePopupDelegate& OnBeforePopup() = 0; + + /** A delegate that is invoked before the browser loads a resource. Its primary purpose is to inject headers into the request. */ + typedef TMap<FString, FString> FRequestHeaders; + DECLARE_DELEGATE_FourParams(FOnBeforeResourceLoadDelegate, FString /*Url*/, FString /*ResourceType*/, FRequestHeaders& /*AdditionalHeaders*/, const bool /*AllowUserCredentials*/); + virtual FOnBeforeResourceLoadDelegate& OnBeforeResourceLoad() = 0; + + /** A delegate that is invoked on completion of browser resource loads. Its primary purpose is to allow response to failures. */ + DECLARE_DELEGATE_FourParams(FOnResourceLoadCompleteDelegate, FString /*Url*/, FString /*ResourceType*/, FString /*RequestStatus*/, int64 /*ContentLength*/); + virtual FOnResourceLoadCompleteDelegate& OnResourceLoadComplete() = 0; + + /** A delegate that is invoked for each console message */ + DECLARE_DELEGATE_FourParams(FOnConsoleMessageDelegate, const FString& /*Message*/, const FString& /*Source*/, int32 /*Line*/, EWebInterfaceBrowserConsoleLogSeverity /*severity*/); + virtual FOnConsoleMessageDelegate& OnConsoleMessage() = 0; + + /** A delegate that is invoked when an existing browser requests creation of a new browser window. */ + DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnCreateWindow, const TWeakPtr<IWebInterfaceBrowserWindow>& /*NewBrowserWindow*/, const TWeakPtr<IWebInterfaceBrowserPopupFeatures>& /* PopupFeatures*/) + virtual FOnCreateWindow& OnCreateWindow() = 0; + + /** A delegate that is invoked when closing created popup windows. */ + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCloseWindow, const TWeakPtr<IWebInterfaceBrowserWindow>& /*BrowserWindow*/) + virtual FOnCloseWindow& OnCloseWindow() = 0; + + /** A delegate that is invoked when the browser needs to show a popup menu. */ + DECLARE_EVENT_OneParam(IWebInterfaceBrowserWindow, FOnShowPopup, const FIntRect& /*PopupSize*/) + virtual FOnShowPopup& OnShowPopup() = 0; + + /** A delegate that is invoked when the browser no longer wants to show the popup menu. */ + DECLARE_EVENT(IWebInterfaceBrowserWindow, FOnDismissPopup) + virtual FOnDismissPopup& OnDismissPopup() = 0; + + /** A delegate that is invoked when the browser needs to show a dialog. */ + DECLARE_DELEGATE_RetVal_OneParam(EWebInterfaceBrowserDialogEventResponse, FOnShowDialog, const TWeakPtr<IWebInterfaceBrowserDialog>& /*DialogParams*/) + virtual FOnShowDialog& OnShowDialog() = 0; + + /** A delegate that is invoked when the browser needs to dismiss and reset all dialogs. */ + DECLARE_DELEGATE(FOnDismissAllDialogs) + virtual FOnDismissAllDialogs& OnDismissAllDialogs() = 0; + + /** Should return true if this dialog wants to suppress the context menu */ + DECLARE_DELEGATE_RetVal(bool, FOnSuppressContextMenu); + virtual FOnSuppressContextMenu& OnSuppressContextMenu() = 0; + + /** A delegate that is invoked for each key down event not handled by the browser, return true if event is handled to prevent it from bubbling up. */ + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnUnhandledKeyDown, const FKeyEvent& /*KeyEvent*/); + virtual FOnUnhandledKeyDown& OnUnhandledKeyDown() = 0; + + /** A delegate that is invoked for each up down event not handled by the browser, return true if event is handled to prevent it from bubbling up. */ + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnUnhandledKeyUp, const FKeyEvent& /*KeyEvent*/); + virtual FOnUnhandledKeyUp& OnUnhandledKeyUp() = 0; + + /** A delegate that is invoked for each key char event not handled by the browser, return true if event is handled to prevent it from bubbling up. */ + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnUnhandledKeyChar, const FCharacterEvent& /*CharacterEvent*/); + virtual FOnUnhandledKeyChar& OnUnhandledKeyChar() = 0; + /** A delegate that is invoked when drag is detected in an area specified as a drag region on the web page. */ + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnDragWindow, const FPointerEvent& /*MouseEvent*/) + virtual FOnDragWindow& OnDragWindow() = 0; + + /** A delegate that is invoked to check the visibility of the native browser */ + DECLARE_DELEGATE_RetVal(bool, FOnCheckVisibility); + virtual FOnCheckVisibility& OnCheckVisibility() + { + return OnCheckVisibilityDelegate; + } + + virtual bool CheckVisibility() + { + return !OnCheckVisibilityDelegate.IsBound() || OnCheckVisibilityDelegate.Execute(); + } + +protected: + + /** Virtual Destructor. */ + virtual ~IWebInterfaceBrowserWindow() { }; + +private: + /** Delegate for veritying the window's visibility */ + FOnCheckVisibility OnCheckVisibilityDelegate; + +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/SWebInterfaceBrowser.h b/Plugins/WebUI/Source/WebBrowserUI/Public/SWebInterfaceBrowser.h new file mode 100644 index 0000000000000000000000000000000000000000..0ec0119303cc33db9220a6eddb0386ae0d7af81d --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/SWebInterfaceBrowser.h @@ -0,0 +1,317 @@ +// Engine/Source/Runtime/WebBrowser/Public/SWebBrowser.h + +#pragma once + +#include "CoreMinimal.h" +#include "Layout/Visibility.h" +#include "Input/Reply.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "SWebInterfaceBrowserView.h" + +class IWebInterfaceBrowserAdapter; +class IWebInterfaceBrowserDialog; +class IWebInterfaceBrowserWindow; +class SEditableTextBox; +struct FWebNavigationRequest; +enum class EWebInterfaceBrowserDialogEventResponse; + +DECLARE_DELEGATE_RetVal(bool, FOnSuppressContextMenu); + +class WEBBROWSERUI_API SWebInterfaceBrowser + : public SCompoundWidget +{ +public: + DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnBeforeBrowse, const FString&, const FWebNavigationRequest& /*Request*/) + DECLARE_DELEGATE_RetVal_ThreeParams(bool, FOnLoadUrl, const FString& /*Method*/, const FString& /*Url*/, FString& /* Response */) + DECLARE_DELEGATE_RetVal_OneParam(EWebInterfaceBrowserDialogEventResponse, FOnShowDialog, const TWeakPtr<IWebInterfaceBrowserDialog>&); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnDragWindow, const FPointerEvent& /* MouseEvent */); + + SLATE_BEGIN_ARGS(SWebInterfaceBrowser) + : _InitialURL(TEXT("https://www.google.com")) + , _ShowControls(true) + , _ShowAddressBar(false) + , _ShowErrorMessage(true) + , _SupportsTransparency(false) + , _SupportsThumbMouseButtonNavigation(true) + , _ShowInitialThrobber(true) + , _BackgroundColor(255,255,255,255) + , _BrowserFrameRate(24) + , _PopupMenuMethod(TOptional<EPopupMethod>()) + , _ViewportSize(FVector2D::ZeroVector) + { } + + /** A reference to the parent window. */ + SLATE_ARGUMENT(TSharedPtr<SWindow>, ParentWindow) + + /** URL that the browser will initially navigate to. The URL should include the protocol, eg http:// */ + SLATE_ARGUMENT(FString, InitialURL) + + /** Optional string to load contents as a web page. */ + SLATE_ARGUMENT(TOptional<FString>, ContentsToLoad) + + /** Whether to show standard controls like Back, Forward, Reload etc. */ + SLATE_ARGUMENT(bool, ShowControls) + + /** Whether to show an address bar. */ + SLATE_ARGUMENT(bool, ShowAddressBar) + + /** Whether to show an error message in case of loading errors. */ + SLATE_ARGUMENT(bool, ShowErrorMessage) + + /** Should this browser window support transparency. */ + SLATE_ARGUMENT(bool, SupportsTransparency) + + /** Whether to allow forward and back navigation via the mouse thumb buttons. */ + SLATE_ARGUMENT(bool, SupportsThumbMouseButtonNavigation) + + /** Whether to show a throbber overlay during browser initialization. */ + SLATE_ARGUMENT(bool, ShowInitialThrobber) + + /** Opaque background color used before a document is loaded and when no document color is specified. */ + SLATE_ARGUMENT(FColor, BackgroundColor) + + /** The frames per second rate that the browser will attempt to use. */ + SLATE_ARGUMENT(int , BrowserFrameRate) + + /** Override the popup menu method used for popup menus. If not set, parent widgets will be queried instead. */ + SLATE_ARGUMENT(TOptional<EPopupMethod>, PopupMenuMethod) + + /** Desired size of the web browser viewport. */ + SLATE_ATTRIBUTE(FVector2D, ViewportSize); + + /** Called when document loading completed. */ + SLATE_EVENT(FSimpleDelegate, OnLoadCompleted) + + /** Called when document loading failed. */ + SLATE_EVENT(FSimpleDelegate, OnLoadError) + + /** Called when document loading started. */ + SLATE_EVENT(FSimpleDelegate, OnLoadStarted) + + /** Called when document title changed. */ + SLATE_EVENT(FOnTextChanged, OnTitleChanged) + + /** Called when the Url changes. */ + SLATE_EVENT(FOnTextChanged, OnUrlChanged) + + /** Called before a popup window happens */ + SLATE_EVENT(FOnBeforePopupDelegate, OnBeforePopup) + + /** Called when the browser requests the creation of a new window */ + SLATE_EVENT(FOnCreateWindowDelegate, OnCreateWindow) + + /** Called when a browser window close event is detected */ + SLATE_EVENT(FOnCloseWindowDelegate, OnCloseWindow) + + /** Called before browser navigation. */ + SLATE_EVENT(FOnBeforeBrowse, OnBeforeNavigation) + + /** Called to allow bypassing page content on load. */ + SLATE_EVENT(FOnLoadUrl, OnLoadUrl) + + /** Called when the browser needs to show a dialog to the user. */ + SLATE_EVENT(FOnShowDialog, OnShowDialog) + + /** Called to dismiss any dialogs shown via OnShowDialog. */ + SLATE_EVENT(FSimpleDelegate, OnDismissAllDialogs) + + SLATE_EVENT(FOnSuppressContextMenu, OnSuppressContextMenu); + + /** Called when drag is detected in a web page area tagged as a drag region. */ + SLATE_EVENT(FOnDragWindow, OnDragWindow); + + /** Called for each console message */ + SLATE_EVENT(FOnConsoleMessageDelegate, OnConsoleMessage); + + + SLATE_END_ARGS() + + + /** Default constructor. */ + SWebInterfaceBrowser(); + + ~SWebInterfaceBrowser(); + + virtual bool SupportsKeyboardFocus() const override {return true;} + + /** + * Construct the widget. + * + * @param InArgs Declaration from which to construct the widget. + */ + void Construct(const FArguments& InArgs, const TSharedPtr<IWebInterfaceBrowserWindow>& InWebBrowserWindow = nullptr); + + /** + * Load the specified URL. + * + * @param NewURL New URL to load. + */ + void LoadURL(FString NewURL); + + /** + * Load a string as data to create a web page. + * + * @param Contents String to load. + * @param DummyURL Dummy URL for the page. + */ + void LoadString(FString Contents, FString DummyURL); + + /** Reload the current page. */ + void Reload(); + + /** Stop loading the page. */ + void StopLoad(); + + /** Get the current title of the web page. */ + FText GetTitleText() const; + + /** + * Gets the currently loaded URL. + * + * @return The URL, or empty string if no document is loaded. + */ + FString GetUrl() const; + + /** + * Gets the URL that appears in the address bar, this may not be the URL that is currently loaded in the frame. + * + * @return The address bar URL. + */ + FText GetAddressBarUrlText() const; + + /** Whether the document finished loading. */ + bool IsLoaded() const; + + /** Whether the document is currently being loaded. */ + bool IsLoading() const; + + /** Execute javascript on the current window */ + void ExecuteJavascript(const FString& ScriptText); + + /** + * Gets the source of the main frame as raw HTML. + * + * This method has to be called asynchronously by passing a callback function, which will be called at a later point when the + * result is ready. + * @param Callback A callable that takes a single string reference for handling the result. + */ + void GetSource(TFunction<void (const FString&)> Callback) const; + + /** + * Expose a UObject instance to the browser runtime. + * Properties and Functions will be accessible from JavaScript side. + * As all communication with the rendering procesis asynchronous, return values (both for properties and function results) are wrapped into JS Future objects. + * + * @param Name The name of the object. The object will show up as window.ue.{Name} on the javascript side. If there is an existing object of the same name, this object will replace it. If bIsPermanent is false and there is an existing permanent binding, the permanent binding will be restored when the temporary one is removed. + * @param Object The object instance. + * @param bIsPermanent If true, the object will be visible to all pages loaded through this browser widget, otherwise, it will be deleted when navigating away from the current page. Non-permanent bindings should be registered from inside an OnLoadStarted event handler in order to be available before JS code starts loading. + */ + void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true); + + /** + * Remove an existing script binding registered by BindUObject. + * + * @param Name The name of the object to remove. + * @param Object The object will only be removed if it is the same object as the one passed in. + * @param bIsPermanent Must match the bIsPermanent argument passed to BindUObject. + */ + void UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true); + + void BindAdapter(const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter); + + void UnbindAdapter(const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter); + + void BindInputMethodSystem(ITextInputMethodSystem* TextInputMethodSystem); + + void UnbindInputMethodSystem(); + + /** Returns true if the browser can navigate backwards. */ + bool CanGoBack() const; + + /** Navigate backwards. */ + void GoBack(); + + /** Returns true if the browser can navigate forwards. */ + bool CanGoForward() const; + + /** Navigate forwards. */ + void GoForward(); + + /** Set parent SWindow for this browser. */ + void SetParentWindow(TSharedPtr<SWindow> Window); + +private: + + /** Navigate backwards. */ + FReply OnBackClicked(); + + /** Navigate forwards. */ + FReply OnForwardClicked(); + + /** Get text for reload button depending on status */ + FText GetReloadButtonText() const; + + /** Reload or stop loading */ + FReply OnReloadClicked(); + + /** Invoked whenever text is committed in the address bar. */ + void OnUrlTextCommitted( const FText& NewText, ETextCommit::Type CommitType ); + + /** Get whether the page viewport should be visible */ + EVisibility GetViewportVisibility() const; + + /** Get whether loading throbber should be visible */ + EVisibility GetLoadingThrobberVisibility() const; + +private: + + /** The actual web browser view */ + TSharedPtr<SWebInterfaceBrowserView> BrowserView; + + /** Editable text widget used for an address bar */ + TSharedPtr< SEditableTextBox > InputText; + + /** A delegate that is invoked when document loading completed. */ + FSimpleDelegate OnLoadCompleted; + + /** A delegate that is invoked when document loading failed. */ + FSimpleDelegate OnLoadError; + + /** A delegate that is invoked when document loading started. */ + FSimpleDelegate OnLoadStarted; + + /** A delegate that is invoked when document title changed. */ + FOnTextChanged OnTitleChanged; + + /** A delegate that is invoked when document address changed. */ + FOnTextChanged OnUrlChanged; + + /** A delegate that is invoked when the browser attempts to pop up a new window */ + FOnBeforePopupDelegate OnBeforePopup; + + /** A delegate that is invoked when the browser writes console output */ + FOnConsoleMessageDelegate OnConsoleMessage; + + /** A delegate that is invoked when the browser requests a UI window for another browser it spawned */ + FOnCreateWindowDelegate OnCreateWindow; + + /** A delegate that is invoked when a window close event is detected */ + FOnCloseWindowDelegate OnCloseWindow; + + /** A delegate that is invoked prior to browser navigation */ + FOnBeforeBrowse OnBeforeNavigation; + + /** A delegate that is invoked when loading a resource, allowing the application to provide contents directly */ + FOnLoadUrl OnLoadUrl; + + /** A delegate that is invoked when when the browser needs to present a dialog to the user */ + FOnShowDialog OnShowDialog; + + /** A delegate that is invoked when when the browser needs to dismiss all dialogs */ + FSimpleDelegate OnDismissAllDialogs; + + /** The initial throbber setting */ + bool bShowInitialThrobber; + +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/SWebInterfaceBrowserView.h b/Plugins/WebUI/Source/WebBrowserUI/Public/SWebInterfaceBrowserView.h new file mode 100644 index 0000000000000000000000000000000000000000..554d2b44de82802cdaeb0a65815598672dbee129 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/SWebInterfaceBrowserView.h @@ -0,0 +1,453 @@ +// Engine/Source/Runtime/WebBrowser/Public/SWebBrowserView.h +#pragma once + +#include "CoreMinimal.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Input/PopupMethodReply.h" +#include "Widgets/SWidget.h" +#include "Widgets/SCompoundWidget.h" +#include "Framework/Application/IMenu.h" +#include "Framework/SlateDelegates.h" +#include "Widgets/SViewport.h" +#include "IWebInterfaceBrowserSingleton.h" + +class FWebInterfaceBrowserViewport; +class IWebInterfaceBrowserAdapter; +class IWebInterfaceBrowserDialog; +class IWebInterfaceBrowserPopupFeatures; +class IWebInterfaceBrowserWindow; +struct FWebNavigationRequest; +enum class EWebInterfaceBrowserDialogEventResponse; +enum class EWebInterfaceBrowserDocumentState; +enum class EWebInterfaceBrowserConsoleLogSeverity; + +DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnBeforePopupDelegate, FString, FString); +DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnCreateWindowDelegate, const TWeakPtr<IWebInterfaceBrowserWindow>&, const TWeakPtr<IWebInterfaceBrowserPopupFeatures>&); +DECLARE_DELEGATE_RetVal_OneParam(bool, FOnCloseWindowDelegate, const TWeakPtr<IWebInterfaceBrowserWindow>&); +DECLARE_DELEGATE_RetVal_OneParam(TSharedPtr<IToolTip>, FOnCreateToolTip, const FText&); +DECLARE_DELEGATE_FourParams(FOnConsoleMessageDelegate, const FString& /*Message*/, const FString& /*Source*/, int32 /*Line*/, EWebInterfaceBrowserConsoleLogSeverity /*Severity*/); + +#if WITH_CEF3 +typedef SViewport SWebInterfaceBrowserWidget; +#else +typedef SWidget SWebInterfaceBrowserWidget; +#endif + +class WEBBROWSERUI_API SWebInterfaceBrowserView + : public SCompoundWidget +{ +public: + DECLARE_DELEGATE_RetVal_TwoParams(bool, FOnBeforeBrowse, const FString& /*Url*/, const FWebNavigationRequest& /*Request*/) + DECLARE_DELEGATE_RetVal_ThreeParams(bool, FOnLoadUrl, const FString& /*Method*/, const FString& /*Url*/, FString& /* Response */) + DECLARE_DELEGATE_RetVal_OneParam(EWebInterfaceBrowserDialogEventResponse, FOnShowDialog, const TWeakPtr<IWebInterfaceBrowserDialog>&); + DECLARE_DELEGATE_RetVal(bool, FOnSuppressContextMenu); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnDragWindow, const FPointerEvent& /* MouseEvent */); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnUnhandledKeyDown, const FKeyEvent& /*KeyEvent*/); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnUnhandledKeyUp, const FKeyEvent& /*KeyEvent*/); + DECLARE_DELEGATE_RetVal_OneParam(bool, FOnUnhandledKeyChar, const FCharacterEvent& /*CharacterEvent*/); + + + SLATE_BEGIN_ARGS(SWebInterfaceBrowserView) + : _InitialURL(TEXT("https://www.google.com")) + , _ShowErrorMessage(true) + , _SupportsTransparency(false) + , _InterceptLoadRequests(false) + , _SupportsThumbMouseButtonNavigation(true) + , _BackgroundColor(255,255,255,255) + , _BrowserFrameRate(24) + , _PopupMenuMethod(TOptional<EPopupMethod>()) + , _ContextSettings() + , _AltRetryDomains(TArray<FString>()) + , _ViewportSize(FVector2D::ZeroVector) + { } + + /** A reference to the parent window. */ + SLATE_ARGUMENT(TSharedPtr<SWindow>, ParentWindow) + + /** URL that the browser will initially navigate to. */ + SLATE_ARGUMENT(FString, InitialURL) + + /** Optional string to load contents as a web page. */ + SLATE_ARGUMENT(TOptional<FString>, ContentsToLoad) + + /** Whether to show an error message in case of loading errors. */ + SLATE_ARGUMENT(bool, ShowErrorMessage) + + /** Should this browser window support transparency. */ + SLATE_ARGUMENT(bool, SupportsTransparency) + + /** Should this browser window intercept resource loading requests. If false the BrowserContext will instead. */ + SLATE_ARGUMENT(bool, InterceptLoadRequests) + + /** Whether to allow forward and back navigation via the mouse thumb buttons. */ + SLATE_ARGUMENT(bool, SupportsThumbMouseButtonNavigation) + + /** Opaque background color used before a document is loaded and when no document color is specified. */ + SLATE_ARGUMENT(FColor, BackgroundColor) + + /** The frames per second rate that the browser will attempt to use. */ + SLATE_ARGUMENT(int, BrowserFrameRate) + + /** Override the popup menu method used for popup menus. If not set, parent widgets will be queried instead. */ + SLATE_ARGUMENT(TOptional<EPopupMethod>, PopupMenuMethod) + + /** Override the default global context settings for this specific window. If not set, the global default will be used. */ + SLATE_ARGUMENT(TOptional<FInterfaceBrowserContextSettings>, ContextSettings) + + /** Domains to retry if original domain cannot connect. */ + SLATE_ARGUMENT(TArray<FString>, AltRetryDomains) + + /** Desired size of the web browser viewport. */ + SLATE_ATTRIBUTE(FVector2D, ViewportSize); + + /** Called when document loading completed. */ + SLATE_EVENT(FSimpleDelegate, OnLoadCompleted) + + /** Called when document loading failed. */ + SLATE_EVENT(FSimpleDelegate, OnLoadError) + + /** Called when document loading started. */ + SLATE_EVENT(FSimpleDelegate, OnLoadStarted) + + /** Called when document title changed. */ + SLATE_EVENT(FOnTextChanged, OnTitleChanged) + + /** Called when the Url changes. */ + SLATE_EVENT(FOnTextChanged, OnUrlChanged) + + /** Called before a popup window happens */ + SLATE_EVENT(FOnBeforePopupDelegate, OnBeforePopup) + + /** Called when the browser requests the creation of a new window */ + SLATE_EVENT(FOnCreateWindowDelegate, OnCreateWindow) + + /** Called when a browser window close event is detected */ + SLATE_EVENT(FOnCloseWindowDelegate, OnCloseWindow) + + /** Called before browser navigation. */ + SLATE_EVENT(FOnBeforeBrowse, OnBeforeNavigation) + + /** Called to allow bypassing page content on load. */ + SLATE_EVENT(FOnLoadUrl, OnLoadUrl) + + /** Called when the browser needs to show a dialog to the user. */ + SLATE_EVENT(FOnShowDialog, OnShowDialog) + + /** Called to dismiss any dialogs shown via OnShowDialog. */ + SLATE_EVENT(FSimpleDelegate, OnDismissAllDialogs) + + /** Called to allow supression of the browser context menu. */ + SLATE_EVENT(FOnSuppressContextMenu, OnSuppressContextMenu); + + /** Called to allow overriding of ToolTip widget construction. */ + SLATE_EVENT(FOnCreateToolTip, OnCreateToolTip) + + /** Called when drag is detected in a web page area tagged as a drag region. */ + SLATE_EVENT(FOnDragWindow, OnDragWindow) + + /** Called to allow the handling of any key down events not handled by the browser. */ + SLATE_EVENT(FOnUnhandledKeyDown, OnUnhandledKeyDown) + + /** Called to allow the handling of any key up events not handled by the browser. */ + SLATE_EVENT(FOnUnhandledKeyUp, OnUnhandledKeyUp) + + /** Called to allow the handling of any key char events not handled by the browser. */ + SLATE_EVENT(FOnUnhandledKeyChar, OnUnhandledKeyChar) + + /** Called for each console message */ + SLATE_EVENT(FOnConsoleMessageDelegate, OnConsoleMessage) + + SLATE_END_ARGS() + + + /** Default constructor. */ + SWebInterfaceBrowserView(); + + ~SWebInterfaceBrowserView(); + + virtual bool SupportsKeyboardFocus() const override {return true;} + + /** + * Construct the widget. + * + * @param InArgs Declaration from which to construct the widget. + */ + void Construct(const FArguments& InArgs, const TSharedPtr<IWebInterfaceBrowserWindow>& InWebBrowserWindow = nullptr); + + virtual int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override; + + /** + * Load the specified URL. + * + * @param NewURL New URL to load. + */ + void LoadURL(FString NewURL); + + /** + * Load a string as data to create a web page. + * + * @param Contents String to load. + * @param DummyURL Dummy URL for the page. + */ + void LoadString(FString Contents, FString DummyURL); + + /** Reload the current page. */ + void Reload(); + + /** Stop loading the page. */ + void StopLoad(); + + /** Get the current title of the web page. */ + FText GetTitleText() const; + + /** + * Gets the currently loaded URL. + * + * @return The URL, or empty string if no document is loaded. + */ + FString GetUrl() const; + + /** + * Gets the URL that appears in the address bar, this may not be the URL that is currently loaded in the frame. + * + * @return The address bar URL. + */ + FText GetAddressBarUrlText() const; + + /** Whether the document finished loading. */ + bool IsLoaded() const; + + /** Whether the document is currently being loaded. */ + bool IsLoading() const; + + /** Whether the browser widget is done initializing. */ + bool IsInitialized() const; + + /** Execute javascript on the current window */ + void ExecuteJavascript(const FString& ScriptText); + + /** + * Gets the source of the main frame as raw HTML. + * + * This method has to be called asynchronously by passing a callback function, which will be called at a later point when the + * result is ready. + * @param Callback A callable that takes a single string reference for handling the result. + */ + void GetSource(TFunction<void (const FString&)> Callback) const ; + + /** + * Expose a UObject instance to the browser runtime. + * Properties and Functions will be accessible from JavaScript side. + * As all communication with the rendering procesis asynchronous, return values (both for properties and function results) are wrapped into JS Future objects. + * + * @param Name The name of the object. The object will show up as window.ue.{Name} on the javascript side. If there is an existing object of the same name, this object will replace it. If bIsPermanent is false and there is an existing permanent binding, the permanent binding will be restored when the temporary one is removed. + * @param Object The object instance. + * @param bIsPermanent If true, the object will be visible to all pages loaded through this browser widget, otherwise, it will be deleted when navigating away from the current page. Non-permanent bindings should be registered from inside an OnLoadStarted event handler in order to be available before JS code starts loading. + */ + void BindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true); + + /** + * Remove an existing script binding registered by BindUObject. + * + * @param Name The name of the object to remove. + * @param Object The object will only be removed if it is the same object as the one passed in. + * @param bIsPermanent Must match the bIsPermanent argument passed to BindUObject. + */ + void UnbindUObject(const FString& Name, UObject* Object, bool bIsPermanent = true); + + void BindAdapter(const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter); + + void UnbindAdapter(const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter); + + void BindInputMethodSystem(ITextInputMethodSystem* TextInputMethodSystem); + + void UnbindInputMethodSystem(); + + /** Returns true if the browser can navigate backwards. */ + bool CanGoBack() const; + + /** Navigate backwards. */ + void GoBack(); + + /** Returns true if the browser can navigate forwards. */ + bool CanGoForward() const; + + /** Navigate forwards. */ + void GoForward(); + + /** Set parent SWindow for this browser. */ + void SetParentWindow(TSharedPtr<SWindow> Window); + + /** Update the underlying browser widget to match the KB focus in slate. + This is used to work around a CEF bug that loses focus state on navigations*/ + void SetBrowserKeyboardFocus(); + + /** Close the underlying browser object before we destruct this view. + This will block until that object is fully destroyed. + Calling this is optional, CEF has object lifetime requirements that mean on shutdown you must destroy browsers before exit.*/ + void CloseBrowser(); + +private: + + void SetupParentWindowHandlers(); + + /** Callback for document loading state changes. */ + void HandleBrowserWindowDocumentStateChanged(EWebInterfaceBrowserDocumentState NewState); + + /** Callback to tell slate we want to update the contents of the web view based on changes inside the view. */ + void HandleBrowserWindowNeedsRedraw(); + + /** Callback for document title changes. */ + void HandleTitleChanged(FString NewTitle); + + /** Callback for loaded url changes. */ + void HandleUrlChanged(FString NewUrl); + + /** Callback for showing browser tool tips. */ + void HandleToolTip(FString ToolTipText); + + /** + * A delegate that is executed prior to browser navigation. + * + * @return true if the navigation was handled an no further action should be taken by the browser, false if the browser should handle. + */ + bool HandleBeforeNavigation(const FString& Url, const FWebNavigationRequest& Request); + + bool HandleLoadUrl(const FString& Method, const FString& Url, FString& OutResponse); + + /** + * A delegate that is executed when the browser requests window creation. + * + * @return true if if the window request was handled, false if the browser requesting the new window should be closed. + */ + bool HandleCreateWindow(const TWeakPtr<IWebInterfaceBrowserWindow>& NewBrowserWindow, const TWeakPtr<IWebInterfaceBrowserPopupFeatures>& PopupFeatures); + + /** + * A delegate that is executed when closing the browser window. + * + * @return true if if the window close was handled, false otherwise. + */ + bool HandleCloseWindow(const TWeakPtr<IWebInterfaceBrowserWindow>& BrowserWindow); + + /** Callback for showing dialogs to the user */ + EWebInterfaceBrowserDialogEventResponse HandleShowDialog(const TWeakPtr<IWebInterfaceBrowserDialog>& DialogParams); + + /** Callback for dismissing any dialogs previously shown */ + void HandleDismissAllDialogs(); + + /** Callback for popup window permission */ + bool HandleBeforePopup(FString URL, FString Target); + + /** Callback for showing a popup menu */ + void HandleShowPopup(const FIntRect& PopupSize); + + /** Callback for hiding the popup menu */ + void HandleDismissPopup(); + + /** Callback from the popup menu notifiying it has been dismissed */ + void HandleMenuDismissed(TSharedRef<IMenu>); + + virtual FPopupMethodReply OnQueryPopupMethod() const override + { + return PopupMenuMethod.IsSet() + ? FPopupMethodReply::UseMethod(PopupMenuMethod.GetValue()) + : FPopupMethodReply::Unhandled(); + } + + void HandleWindowDeactivated(); + void HandleWindowActivated(); + bool UnhandledKeyDown(const FKeyEvent& KeyEvent); + bool UnhandledKeyUp(const FKeyEvent& KeyEvent); + bool UnhandledKeyChar(const FCharacterEvent& CharacterEvent); + + bool HandleDrag(const FPointerEvent& MouseEvent); + void HandleConsoleMessage(const FString& Message, const FString& Source, int32 Line, EWebInterfaceBrowserConsoleLogSeverity Serverity); + + TOptional<FSlateRenderTransform> GetPopupRenderTransform() const; +private: + + /** Interface for dealing with a web browser window. */ + TSharedPtr<IWebInterfaceBrowserWindow> BrowserWindow; + /** The slate window that contains this widget. This must be stored weak otherwise we create a circular reference. */ + mutable TWeakPtr<SWindow> SlateParentWindowPtr; + /** Viewport interface for rendering the web page. */ + TSharedPtr<FWebInterfaceBrowserViewport> BrowserViewport; + /** Viewport interface for rendering popup menus. */ + TSharedPtr<FWebInterfaceBrowserViewport> MenuViewport; + /** The implementation dependent widget that renders the browser contents. */ + TSharedPtr<SWebInterfaceBrowserWidget> BrowserWidget; + + TArray<TSharedRef<IWebInterfaceBrowserAdapter>> Adapters; + + /** + * An interface pointer to a menu object presenting a popup. + * Pointer is null when a popup is not visible. + */ + TWeakPtr<IMenu> PopupMenuPtr; + + /** Can be set to override the popup menu method used for popup menus. If not set, parent widgets will be queried instead. */ + TOptional<EPopupMethod> PopupMenuMethod; + + /** The url that appears in the address bar which can differ from the url of the loaded page */ + FText AddressBarUrl; + + /** A delegate that is invoked when document loading completed. */ + FSimpleDelegate OnLoadCompleted; + + /** A delegate that is invoked when document loading failed. */ + FSimpleDelegate OnLoadError; + + /** A delegate that is invoked when document loading started. */ + FSimpleDelegate OnLoadStarted; + + /** A delegate that is invoked when document title changed. */ + FOnTextChanged OnTitleChanged; + + /** A delegate that is invoked when document address changed. */ + FOnTextChanged OnUrlChanged; + + /** A delegate that is invoked when the browser attempts to pop up a new window */ + FOnBeforePopupDelegate OnBeforePopup; + + /** A delegate that is invoked when the browser requests a UI window for another browser it spawned */ + FOnCreateWindowDelegate OnCreateWindow; + + /** A delegate that is invoked when a window close event is detected */ + FOnCloseWindowDelegate OnCloseWindow; + + /** A delegate that is invoked prior to browser navigation */ + FOnBeforeBrowse OnBeforeNavigation; + + /** A delegate that is invoked when loading a resource, allowing the application to provide contents directly */ + FOnLoadUrl OnLoadUrl; + + /** A delegate that is invoked when when the browser needs to present a dialog to the user */ + FOnShowDialog OnShowDialog; + + /** A delegate that is invoked when when the browser needs to dismiss all dialogs */ + FSimpleDelegate OnDismissAllDialogs; + + FOnSuppressContextMenu OnSuppressContextMenu; + + /** A delegate that is invoked when when the browser wishes to create a tooltip */ + FOnCreateToolTip OnCreateToolTip; + + /** A delegate that is invoked when the browser detects drag event in within drag region */ + FOnDragWindow OnDragWindow; + + /** A delegate for handling key down events not handled by browser. */ + FOnUnhandledKeyDown OnUnhandledKeyDown; + + /** A delegate for handling key up events not handled by browser. */ + FOnUnhandledKeyUp OnUnhandledKeyUp; + + /** A delegate for handling key char events not handled by browser. */ + FOnUnhandledKeyChar OnUnhandledKeyChar; + + /** A delegate that is invoked for each console message */ + FOnConsoleMessageDelegate OnConsoleMessage; + +protected: + bool HandleSuppressContextMenu(); + +}; \ No newline at end of file diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceBrowserModule.h b/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceBrowserModule.h new file mode 100644 index 0000000000000000000000000000000000000000..0cc72c3f40308e724042a5f4e3566f1e02971ae1 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceBrowserModule.h @@ -0,0 +1,74 @@ +// Engine/Source/Runtime/WebBrowser/Public/WebBrowserModule.h + +#pragma once + +#include "CoreMinimal.h" +#include "Modules/ModuleInterface.h" +#include "Modules/ModuleManager.h" + +class IWebInterfaceBrowserSingleton; + +/** + * WebBrowser initialization settings, can be used to override default init behaviors. + */ +struct WEBBROWSERUI_API FWebInterfaceBrowserInitSettings +{ +public: + /** + * Default constructor. Initializes all members with default behavior values. + */ + FWebInterfaceBrowserInitSettings(); + + // The string which is appended to the browser's user-agent value. + FString ProductVersion; +}; + +/** + * WebBrowserModule interface + */ +class IWebInterfaceBrowserModule : public IModuleInterface +{ +public: + /** + * Get or load the Web Browser Module + * + * @return The loaded module + */ + static inline IWebInterfaceBrowserModule& Get() + { + return FModuleManager::LoadModuleChecked< IWebInterfaceBrowserModule >("WebBrowserUI"); + } + + /** + * Check whether the module has already been loaded + * + * @return True if the module is loaded + */ + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded("WebBrowserUI"); + } + + /** + * Customize initialization settings. You must call this before the first GetSingleton call, in order to override init settings. + * + * @param WebBrowserInitSettings The custom settings. + * @return true if the settings were used to initialize the singleton. False if the call was ignored due to singleton already existing. + */ + virtual bool CustomInitialize(const FWebInterfaceBrowserInitSettings& WebBrowserInitSettings) = 0; + + /** + * Get the Web Browser Singleton + * + * @return The Web Browser Singleton + */ + virtual IWebInterfaceBrowserSingleton* GetSingleton() = 0; + + + /** + * Check whether the web module loaded its requirements successfully + * + * @return True if the module load worked + */ + virtual bool IsWebModuleAvailable() const = 0; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceBrowserViewport.h b/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceBrowserViewport.h new file mode 100644 index 0000000000000000000000000000000000000000..e01ff5c8209fe84cce5a82d4c1035afe5cf39b60 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceBrowserViewport.h @@ -0,0 +1,63 @@ +// Engine/Source/Runtime/WebBrowser/Public/WebBrowserViewport.h + +#pragma once + +#include "CoreMinimal.h" +#include "Input/CursorReply.h" +#include "Input/Reply.h" +#include "Rendering/RenderingCommon.h" + +class FSlateShaderResource; +class IWebInterfaceBrowserWindow; + +/** + * A Slate viewport to display a Web Browser Window + */ +class WEBBROWSERUI_API FWebInterfaceBrowserViewport : public ISlateViewport +{ +public: + /** + * Default Constructor + * + * @param InWebBrowserWindow The Web Browser Window this viewport will display + * @param InViewportWidget The Widget displaying this viewport (needed to capture mouse) + * @param InIsPopup Used to initialize a viewport for showing browser popup menus instead of the main window. + */ + FWebInterfaceBrowserViewport(TSharedPtr<IWebInterfaceBrowserWindow> InWebBrowserWindow, bool InIsPopup = false) + : WebBrowserWindow(InWebBrowserWindow) + , bIsPopup(InIsPopup) + { } + + /** + * Destructor. + */ + ~FWebInterfaceBrowserViewport( ) + { + } + + // ISlateViewport interface + virtual FIntPoint GetSize() const override; + virtual FSlateShaderResource* GetViewportRenderTargetTexture() const override; + virtual void Tick( const FGeometry& AllottedGeometry, double InCurrentTime, float DeltaTime ) override; + virtual bool RequiresVsync() const override; + virtual FCursorReply OnCursorQuery( const FGeometry& MyGeometry, const FPointerEvent& CursorEvent ) override; + virtual FReply OnMouseButtonDown( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; + virtual FReply OnMouseButtonUp( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; + virtual void OnMouseEnter( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; + virtual void OnMouseLeave( const FPointerEvent& MouseEvent ) override; + virtual FReply OnMouseMove( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; + virtual FReply OnMouseWheel( const FGeometry& MyGeometry, const FPointerEvent& MouseEvent ) override; + virtual FReply OnTouchGesture( const FGeometry& MyGeometry, const FPointerEvent& GestureEvent ) override; + virtual FReply OnMouseButtonDoubleClick( const FGeometry& InMyGeometry, const FPointerEvent& InMouseEvent ) override; + virtual FReply OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) override; + virtual FReply OnKeyUp( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) override; + virtual FReply OnKeyChar( const FGeometry& MyGeometry, const FCharacterEvent& InCharacterEvent ) override; + virtual FReply OnFocusReceived( const FFocusEvent& InFocusEvent ) override; + virtual void OnFocusLost( const FFocusEvent& InFocusEvent ) override; + +private: + /** The web browser this viewport will display */ + TSharedPtr<IWebInterfaceBrowserWindow> WebBrowserWindow; + /** Whether this viewport is showing the browser window or a popup menu widget */ + bool const bIsPopup; +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceJSFunction.h b/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceJSFunction.h new file mode 100644 index 0000000000000000000000000000000000000000..f01a6dfe264dbdf29666e1b10b24a5235c183e69 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/Public/WebInterfaceJSFunction.h @@ -0,0 +1,241 @@ +// Engine/Source/Runtime/WebBrowser/Public/WebJSFunction.h + +#pragma once + +#include "Containers/Array.h" +#include "Containers/Map.h" +#include "Containers/UnrealString.h" +#include "CoreMinimal.h" +#include "HAL/Platform.h" +#include "Internationalization/Text.h" +#include "Misc/Guid.h" +#include "Templates/EnableIf.h" +#include "Templates/IsPointer.h" +#include "Templates/SharedPointer.h" +#include "UObject/Class.h" +#include "UObject/NameTypes.h" +#include "UObject/ObjectMacros.h" +#include "WebInterfaceJSFunction.generated.h" + +class FWebInterfaceJSScripting; +class UObject; +class UStruct; + +struct WEBBROWSERUI_API FWebInterfaceJSParam +{ + + struct IStructWrapper + { + virtual ~IStructWrapper() {}; + virtual UStruct* GetTypeInfo() = 0; + virtual const void* GetData() = 0; + virtual IStructWrapper* Clone() = 0; + }; + + template <typename T> struct FStructWrapper + : public IStructWrapper + { + T StructValue; + FStructWrapper(const T& InValue) + : StructValue(InValue) + {} + virtual ~FStructWrapper() + {} + virtual UStruct* GetTypeInfo() override + { + return T::StaticStruct(); + } + virtual const void* GetData() override + { + return &StructValue; + } + virtual IStructWrapper* Clone() override + { + return new FStructWrapper<T>(StructValue); + } + }; + + FWebInterfaceJSParam() : Tag(PTYPE_NULL) {} + FWebInterfaceJSParam(bool Value) : Tag(PTYPE_BOOL), BoolValue(Value) {} + FWebInterfaceJSParam(int8 Value) : Tag(PTYPE_INT), IntValue(Value) {} + FWebInterfaceJSParam(int16 Value) : Tag(PTYPE_INT), IntValue(Value) {} + FWebInterfaceJSParam(int32 Value) : Tag(PTYPE_INT), IntValue(Value) {} + FWebInterfaceJSParam(uint8 Value) : Tag(PTYPE_INT), IntValue(Value) {} + FWebInterfaceJSParam(uint16 Value) : Tag(PTYPE_INT), IntValue(Value) {} + FWebInterfaceJSParam(uint32 Value) : Tag(PTYPE_DOUBLE), DoubleValue(Value) {} + FWebInterfaceJSParam(int64 Value) : Tag(PTYPE_DOUBLE), DoubleValue(Value) {} + FWebInterfaceJSParam(uint64 Value) : Tag(PTYPE_DOUBLE), DoubleValue(Value) {} + FWebInterfaceJSParam(double Value) : Tag(PTYPE_DOUBLE), DoubleValue(Value) {} + FWebInterfaceJSParam(float Value) : Tag(PTYPE_DOUBLE), DoubleValue(Value) {} + FWebInterfaceJSParam(const FString& Value) : Tag(PTYPE_STRING), StringValue(new FString(Value)) {} + FWebInterfaceJSParam(const FText& Value) : Tag(PTYPE_STRING), StringValue(new FString(Value.ToString())) {} + FWebInterfaceJSParam(const FName& Value) : Tag(PTYPE_STRING), StringValue(new FString(Value.ToString())) {} + FWebInterfaceJSParam(const TCHAR* Value) : Tag(PTYPE_STRING), StringValue(new FString(Value)) {} + FWebInterfaceJSParam(UObject* Value) : Tag(PTYPE_OBJECT), ObjectValue(Value) {} + template <typename T> FWebInterfaceJSParam(const T& Value, + typename TEnableIf<!TIsPointer<T>::Value, UStruct>::Type* InTypeInfo=T::StaticStruct()) + : Tag(PTYPE_STRUCT) + , StructValue(new FStructWrapper<T>(Value)) + {} + template <typename T> FWebInterfaceJSParam(const TArray<T>& Value) + : Tag(PTYPE_ARRAY) + { + ArrayValue = new TArray<FWebInterfaceJSParam>(); + ArrayValue->Reserve(Value.Num()); + for(T Item : Value) + { + ArrayValue->Add(FWebInterfaceJSParam(Item)); + } + } + template <typename T> FWebInterfaceJSParam(const TMap<FString, T>& Value) + : Tag(PTYPE_MAP) + { + MapValue = new TMap<FString, FWebInterfaceJSParam>(); + MapValue->Reserve(Value.Num()); + for(const auto& Pair : Value) + { + MapValue->Add(Pair.Key, FWebInterfaceJSParam(Pair.Value)); + } + } + template <typename K, typename T> FWebInterfaceJSParam(const TMap<K, T>& Value) + : Tag(PTYPE_MAP) + { + MapValue = new TMap<FString, FWebInterfaceJSParam>(); + MapValue->Reserve(Value.Num()); + for(const auto& Pair : Value) + { + MapValue->Add(Pair.Key.ToString(), FWebInterfaceJSParam(Pair.Value)); + } + } + FWebInterfaceJSParam(const FWebInterfaceJSParam& Other); + FWebInterfaceJSParam(FWebInterfaceJSParam&& Other); + ~FWebInterfaceJSParam(); + + enum { PTYPE_NULL, PTYPE_BOOL, PTYPE_INT, PTYPE_DOUBLE, PTYPE_STRING, PTYPE_OBJECT, PTYPE_STRUCT, PTYPE_ARRAY, PTYPE_MAP } Tag; + union + { + bool BoolValue; + double DoubleValue; + int32 IntValue; + UObject* ObjectValue; + const FString* StringValue; + IStructWrapper* StructValue; + TArray<FWebInterfaceJSParam>* ArrayValue; + TMap<FString, FWebInterfaceJSParam>* MapValue; + }; + +}; + +class FWebInterfaceJSScripting; + +/** Base class for JS callback objects. */ +USTRUCT() +struct WEBBROWSERUI_API FWebInterfaceJSCallbackBase +{ + GENERATED_USTRUCT_BODY() + FWebInterfaceJSCallbackBase() + {} + + bool IsValid() const + { + return ScriptingPtr.IsValid(); + } + + +protected: + FWebInterfaceJSCallbackBase(TSharedPtr<FWebInterfaceJSScripting> InScripting, const FGuid& InCallbackId) + : ScriptingPtr(InScripting) + , CallbackId(InCallbackId) + {} + + void Invoke(int32 ArgCount, FWebInterfaceJSParam Arguments[], bool bIsError = false) const; + +private: + + TWeakPtr<FWebInterfaceJSScripting> ScriptingPtr; + FGuid CallbackId; +}; + + +/** + * Representation of a remote JS function. + * FWebJSFunction objects represent a JS function and allow calling them from native code. + * FWebJSFunction objects can also be added to delegates and events using the Bind/AddLambda method. + */ +USTRUCT() +struct WEBBROWSERUI_API FWebInterfaceJSFunction + : public FWebInterfaceJSCallbackBase +{ + GENERATED_USTRUCT_BODY() + + FWebInterfaceJSFunction() + : FWebInterfaceJSCallbackBase() + {} + + FWebInterfaceJSFunction(TSharedPtr<FWebInterfaceJSScripting> InScripting, const FGuid& InFunctionId) + : FWebInterfaceJSCallbackBase(InScripting, InFunctionId) + {} + + template<typename ...ArgTypes> void operator()(ArgTypes... Args) const + { + FWebInterfaceJSParam ArgArray[sizeof...(Args)] = {FWebInterfaceJSParam(Args)...}; + Invoke(sizeof...(Args), ArgArray); + } +}; + +/** + * Representation of a remote JS async response object. + * UFUNCTIONs taking a FWebJSResponse will get it passed in automatically when called from a web browser. + * Pass a result or error back by invoking Success or Failure on the object. + * UFunctions accepting a FWebJSResponse should have a void return type, as any value returned from the function will be ignored. + * Calling the response methods does not have to happen before returning from the function, which means you can use this to implement asynchronous functionality. + * + * Note that the remote object will become invalid as soon as a result has been delivered, so you can only call either Success or Failure once. + */ +USTRUCT() +struct WEBBROWSERUI_API FWebInterfaceJSResponse + : public FWebInterfaceJSCallbackBase +{ + GENERATED_USTRUCT_BODY() + + FWebInterfaceJSResponse() + : FWebInterfaceJSCallbackBase() + {} + + FWebInterfaceJSResponse(TSharedPtr<FWebInterfaceJSScripting> InScripting, const FGuid& InCallbackId) + : FWebInterfaceJSCallbackBase(InScripting, InCallbackId) + {} + + /** + * Indicate successful completion without a return value. + * The remote Promise's then() handler will be executed without arguments. + */ + void Success() const + { + Invoke(0, nullptr, false); + } + + /** + * Indicate successful completion passing a return value back. + * The remote Promise's then() handler will be executed with the value passed as its single argument. + */ + template<typename T> + void Success(T Arg) const + { + FWebInterfaceJSParam ArgArray[1] = {FWebInterfaceJSParam(Arg)}; + Invoke(1, ArgArray, false); + } + + /** + * Indicate failed completion, passing an error message back to JS. + * The remote Promise's catch() handler will be executed with the value passed as the error reason. + */ + template<typename T> + void Failure(T Arg) const + { + FWebInterfaceJSParam ArgArray[1] = {FWebInterfaceJSParam(Arg)}; + Invoke(1, ArgArray, true); + } + + +}; diff --git a/Plugins/WebUI/Source/WebBrowserUI/WebBrowserUI.Build.cs b/Plugins/WebUI/Source/WebBrowserUI/WebBrowserUI.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..d3364477bc651914b1f1817b993d1d39c340bc75 --- /dev/null +++ b/Plugins/WebUI/Source/WebBrowserUI/WebBrowserUI.Build.cs @@ -0,0 +1,92 @@ +// Engine/Source/Runtime/WebBrowser/WebBrowser.Build.cs + +using UnrealBuildTool; +using System.IO; + +public class WebBrowserUI : ModuleRules +{ + public WebBrowserUI(ReadOnlyTargetRules Target) : base(Target) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "ApplicationCore", + "RHI", + "InputCore", + "Serialization", + "HTTP" + } + ); + + PublicDependencyModuleNames.AddRange( + new string[] + { + "Slate", + "SlateCore" + } + ); + + if (Target.Platform == UnrealTargetPlatform.Android + || Target.Platform == UnrealTargetPlatform.IOS + || Target.Platform == UnrealTargetPlatform.TVOS) + { + // We need these on mobile for external texture support + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Engine", + "Launch", + "WebBrowserTexture" + } + ); + + // We need this one on Android for URL decoding + PrivateDependencyModuleNames.Add("HTTP"); + + CircularlyReferencedDependentModules.Add("WebBrowserTexture"); + } + + if (Target.Type != TargetType.Program && Target.Platform == UnrealTargetPlatform.Win64) + { + PrivateDependencyModuleNames.AddRange( + new string[] + { + "Engine", + "RenderCore" + } + ); + } + + if (Target.Platform == UnrealTargetPlatform.Win64 + || Target.Platform == UnrealTargetPlatform.Mac + || Target.Platform == UnrealTargetPlatform.Linux) + { + PrivateDependencyModuleNames.Add("CEF3Utils"); + AddEngineThirdPartyPrivateStaticDependencies(Target, + "CEF3" + ); + + if (Target.Type != TargetType.Server) + { + if (Target.Platform == UnrealTargetPlatform.Mac) + { + // Add contents of EpicWebHelper.app directory as runtime dependencies + foreach (string FilePath in Directory.EnumerateFiles(Target.RelativeEnginePath + "/Binaries/Mac/EpicWebHelper.app", "*", SearchOption.AllDirectories)) + { + RuntimeDependencies.Add(FilePath); + } + } + else if (Target.Platform == UnrealTargetPlatform.Linux) + { + RuntimeDependencies.Add("$(EngineDir)/Binaries/" + Target.Platform.ToString() + "/EpicWebHelper"); + } + else + { + RuntimeDependencies.Add("$(EngineDir)/Binaries/" + Target.Platform.ToString() + "/EpicWebHelper.exe"); + } + } + } + } +} diff --git a/Plugins/WebUI/Source/WebUI/Private/SWebInterface.cpp b/Plugins/WebUI/Source/WebUI/Private/SWebInterface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..61ea6efd5e9d2a17002c75ba106690aa7d8611a7 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/SWebInterface.cpp @@ -0,0 +1,564 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "SWebInterface.h" +#if !UE_SERVER +#if PLATFORM_ANDROID || PLATFORM_IOS +#include "WebBrowserModule.h" +#include "IWebBrowserSingleton.h" +#else +#include "SWebInterfaceBrowser.h" +#include "WebInterfaceBrowserModule.h" +#include "WebInterfaceSchemeHandler.h" +#include "IWebInterfaceBrowserPopupFeatures.h" +#include "IWebInterfaceBrowserSingleton.h" +#include "IWebInterfaceBrowserWindow.h" +#endif +#include "RenderUtils.h" +#include "Framework/Application/SlateApplication.h" +#include "Input/Events.h" +#include "Input/Reply.h" +#include "Widgets/Layout/SBorder.h" + +#if PLATFORM_ANDROID || PLATFORM_IOS +typedef IWebBrowserModule IWebInterfaceBrowserModule; +typedef IWebBrowserSingleton IWebInterfaceBrowserSingleton; +#endif + +SWebInterface::SWebInterface() +{ + bAcceleratedPaint = true; + bMouseTransparency = false; + bVirtualPointerTransparency = false; + + TransparencyDelay = 0.0f; + TransparencyThreshold = 0.333f; + TransparencyTick = 0.0f; + + LastMousePixel = FLinearColor::White; + LastMouseTime = 0.0f; + LastMouseTick = 0.0f; +} + +SWebInterface::~SWebInterface() +{ +#if UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG + for ( TPair<TWeakPtr<IWebInterfaceBrowserWindow>, TWeakPtr<SWindow>> Temp : BrowserWindowWidgets ) + { + if ( Temp.Key.IsValid() ) + { + TSharedPtr<IWebInterfaceBrowserWindow> WebBrowserWindow = Temp.Key.Pin(); + if ( WebBrowserWindow.IsValid() ) + WebBrowserWindow->CloseBrowser( false ); + } + + if ( Temp.Value.IsValid() ) + { + TSharedPtr<SWindow> Window = Temp.Value.Pin(); + if ( Window.IsValid() ) + Window->RequestDestroyWindow(); + } + } +#endif +} + +bool SWebInterface::bPAK = false; +void SWebInterface::Construct( const FArguments& InArgs ) +{ + OnLoadCompleted = InArgs._OnLoadCompleted; + OnLoadError = InArgs._OnLoadError; + OnLoadStarted = InArgs._OnLoadStarted; + OnTitleChanged = InArgs._OnTitleChanged; + OnUrlChanged = InArgs._OnUrlChanged; + OnBeforeNavigation = InArgs._OnBeforeNavigation; + OnLoadUrl = InArgs._OnLoadUrl; + OnShowDialog = InArgs._OnShowDialog; + OnDismissAllDialogs = InArgs._OnDismissAllDialogs; + OnBeforePopup = InArgs._OnBeforePopup; + OnCreateWindow = InArgs._OnCreateWindow; + OnCloseWindow = InArgs._OnCloseWindow; + + bAcceleratedPaint = InArgs._AcceleratedPaint; + bMouseTransparency = InArgs._EnableMouseTransparency; + bVirtualPointerTransparency = InArgs._EnableVirtualPointerTransparency; + + TransparencyDelay = FMath::Max( 0.0f, InArgs._TransparencyDelay ); + TransparencyThreshold = FMath::Clamp( InArgs._TransparencyThreshold, 0.0f, 1.0f ); + TransparencyTick = FMath::Max( 0.0f, InArgs._TransparencyTick ); + +#if PLATFORM_ANDROID || PLATFORM_IOS + FCreateBrowserWindowSettings Settings; + bAcceleratedPaint = false; +#else + FCreateInterfaceBrowserWindowSettings Settings; + Settings.bAcceleratedPaint = bAcceleratedPaint; + Settings.bUseNativeCursors = InArgs._NativeCursors; +#endif + Settings.BrowserFrameRate = FMath::Clamp( InArgs._FrameRate, 1, 60 ); + Settings.bUseTransparency = true; + Settings.BackgroundColor = InArgs._BackgroundColor; + Settings.InitialURL = InArgs._InitialURL; + Settings.ContentsToLoad = InArgs._ContentsToLoad; + Settings.bShowErrorMessage = UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG; + Settings.bThumbMouseButtonNavigation = false; + + IWebInterfaceBrowserSingleton* Singleton = IWebInterfaceBrowserModule::Get().GetSingleton(); + if ( Singleton ) + { +#if WITH_CEF3 + if ( !bPAK ) + bPAK = Singleton->RegisterSchemeHandlerFactory( "pak", FString(), new FWebInterfaceSchemeHandlerFactory() ); +#endif + + Singleton->SetDevToolsShortcutEnabled( Settings.bShowErrorMessage ); + BrowserWindow = Singleton->CreateBrowserWindow( Settings ); + } + + ChildSlot + [ + SAssignNew( BrowserView, SWebInterfaceBrowserView, BrowserWindow ) + .ParentWindow( InArgs._ParentWindow ) + .InitialURL( InArgs._InitialURL ) + .ContentsToLoad( InArgs._ContentsToLoad ) +#if UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG + .ShowErrorMessage( true ) +#else + .ShowErrorMessage( false ) +#endif + .SupportsTransparency( true ) + .SupportsThumbMouseButtonNavigation( false ) + .BackgroundColor( InArgs._BackgroundColor ) + .PopupMenuMethod( InArgs._PopupMenuMethod ) + .ViewportSize( InArgs._ViewportSize ) + .OnLoadCompleted( OnLoadCompleted ) + .OnLoadError( OnLoadError ) + .OnLoadStarted( OnLoadStarted ) + .OnTitleChanged( OnTitleChanged ) + .OnUrlChanged( OnUrlChanged ) + .OnBeforePopup( this, &SWebInterface::HandleBeforePopup ) + .OnCreateWindow( this, &SWebInterface::HandleCreateWindow ) + .OnCloseWindow( this, &SWebInterface::HandleCloseWindow ) + .OnBeforeNavigation( OnBeforeNavigation ) + .OnLoadUrl( OnLoadUrl ) + .OnShowDialog( OnShowDialog ) + .OnDismissAllDialogs( OnDismissAllDialogs ) + .Visibility( this, &SWebInterface::GetViewportVisibility ) + .OnSuppressContextMenu( this, &SWebInterface::HandleSuppressContextMenu ) + ]; +} + +void SWebInterface::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) +{ + SWidget::Tick( AllottedGeometry, InCurrentTime, InDeltaTime ); + if ( HasMouseTransparency() ) + { + if ( FSlateApplication::IsInitialized() && ( TransparencyTick <= 0.0f + || TransparencyTick <= LastMouseTick ) ) + { + LastMousePixel = FLinearColor::Transparent; + LastMouseTime = LastMouseTime + InDeltaTime; + LastMouseTick = 0.0f; + + TSharedPtr<ICursor> Mouse = FSlateApplication::Get().GetPlatformCursor(); + if ( Mouse.IsValid() && Mouse->GetType() != EMouseCursor::None ) + { + FVector2D MousePosition = Mouse->GetPosition(); + if ( !MousePosition.ContainsNaN() ) + { + FVector2D LocalMouse = AllottedGeometry.AbsoluteToLocal( MousePosition ); + FVector2D LocalSize = AllottedGeometry.GetLocalSize(); + + FVector2D LocalUV = LocalSize.X > 0.0f && LocalSize.Y > 0.0f ? + FVector2D( LocalMouse.X / LocalSize.X, LocalMouse.Y / LocalSize.Y ) : + FVector2D(); + + if ( LocalUV.X >= 0.0f && LocalUV.X <= 1.0f && LocalUV.Y >= 0.0f && LocalUV.Y <= 1.0f ) + { + int32 X = FMath::FloorToInt( LocalUV.X * GetTextureWidth() ); + int32 Y = FMath::FloorToInt( LocalUV.Y * GetTextureHeight() ); + + FLinearColor Pixel = ReadTexturePixel( X, Y ); + if ( ( Pixel.A < TransparencyThreshold && LastMousePixel.A >= TransparencyThreshold ) + || ( Pixel.A >= TransparencyThreshold && LastMousePixel.A < TransparencyThreshold ) ) + LastMouseTime = 0.0f; + + LastMousePixel = Pixel; + } + else + LastMousePixel = FLinearColor::White; + } + } + } + else + LastMouseTick += InDeltaTime; + } + else + LastMousePixel = FLinearColor::White; +} + +EVisibility SWebInterface::GetViewportVisibility() const +{ + if ( !BrowserView.IsValid() || !BrowserView->IsInitialized() ) + return EVisibility::Hidden; + + if ( HasMouseTransparency() && LastMousePixel.A < TransparencyThreshold && LastMouseTime >= TransparencyDelay ) + return EVisibility::HitTestInvisible; + + return EVisibility::Visible; +} + +bool SWebInterface::HandleBeforePopup( FString URL, FString Frame ) +{ +#if UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG + if ( URL.StartsWith( "chrome-devtools://" ) ) + return false; +#endif + + if ( OnBeforePopup.IsBound() ) + return OnBeforePopup.Execute( URL, Frame ); + + return true; +} + +bool SWebInterface::HandleSuppressContextMenu() +{ + return true; +} + +bool SWebInterface::HandleCreateWindow( const TWeakPtr<IWebInterfaceBrowserWindow>& NewBrowserWindow, const TWeakPtr<IWebInterfaceBrowserPopupFeatures>& PopupFeatures ) +{ +#if UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG + if ( !PopupFeatures.IsValid() ) + return false; + + TSharedPtr<IWebInterfaceBrowserPopupFeatures> PopupFeaturesSP = PopupFeatures.Pin(); + if ( !PopupFeaturesSP.IsValid() ) + return false; + + TSharedPtr<SWindow> ParentWindow = FSlateApplication::Get().FindWidgetWindow( SharedThis( this ) ); + if ( !ParentWindow.IsValid() ) + return false; + + const int PosX = PopupFeaturesSP->IsXSet() ? PopupFeaturesSP->GetX() : 100; + const int PosY = PopupFeaturesSP->IsYSet() ? PopupFeaturesSP->GetY() : 100; + const FVector2D BrowserWindowPosition( PosX, PosY ); + + const int Width = PopupFeaturesSP->IsWidthSet() ? PopupFeaturesSP->GetWidth() : 800; + const int Height = PopupFeaturesSP->IsHeightSet() ? PopupFeaturesSP->GetHeight() : 600; + const FVector2D BrowserWindowSize( Width, Height ); + + const ESizingRule SizingRule = PopupFeaturesSP->IsResizable() ? + ESizingRule::UserSized : + ESizingRule::FixedSize; + + TSharedPtr<IWebInterfaceBrowserWindow> NewBrowserWindowSP = NewBrowserWindow.Pin(); + if ( !NewBrowserWindowSP.IsValid() ) + return false; + + TSharedRef<SWindow> NewWindow = + SNew( SWindow ) + .Title( FText::GetEmpty() ) + .ClientSize( BrowserWindowSize ) + .ScreenPosition( BrowserWindowPosition ) + .AutoCenter( EAutoCenter::None ) + .SizingRule( SizingRule ) + .SupportsMaximize( SizingRule != ESizingRule::FixedSize ) + .SupportsMinimize( SizingRule != ESizingRule::FixedSize ) + .HasCloseButton( true ) + .CreateTitleBar( true ) + .IsInitiallyMaximized( PopupFeaturesSP->IsFullscreen() ) + .LayoutBorder( FMargin( 0 ) ); + + TSharedPtr<SWebInterfaceBrowser> WebBrowser; + NewWindow->SetContent( + SNew( SBorder ) + .VAlign( VAlign_Fill ) + .HAlign( HAlign_Fill ) + .Padding( 0 ) + [ + SAssignNew( WebBrowser, SWebInterfaceBrowser, NewBrowserWindowSP ) + .ShowControls( false ) + .ShowAddressBar( false ) + .OnCreateWindow( this, &SWebInterface::HandleCreateWindow ) + .OnCloseWindow( this, &SWebInterface::HandleCloseWindow ) + ] ); + + { + struct FLocal + { + static void RequestDestroyWindowOverride( const TSharedRef<SWindow>& Window, TWeakPtr<IWebInterfaceBrowserWindow> BrowserWindowPtr ) + { + TSharedPtr<IWebInterfaceBrowserWindow> BrowserWindow = BrowserWindowPtr.Pin(); + if ( BrowserWindow.IsValid() ) + { + if ( BrowserWindow->IsClosing() ) + FSlateApplicationBase::Get().RequestDestroyWindow( Window ); + else + BrowserWindow->CloseBrowser( false ); + } + } + }; + + NewWindow->SetRequestDestroyWindowOverride( FRequestDestroyWindowOverride::CreateStatic( &FLocal::RequestDestroyWindowOverride, TWeakPtr<IWebInterfaceBrowserWindow>( NewBrowserWindow ) ) ); + } + + FSlateApplication::Get().AddWindow( NewWindow ); + NewWindow->BringToFront(); + FSlateApplication::Get().SetKeyboardFocus( WebBrowser, EFocusCause::SetDirectly ); + + BrowserWindowWidgets.Add( NewBrowserWindow, NewWindow ); + return true; +#else + return false; +#endif +} + +bool SWebInterface::HandleCloseWindow( const TWeakPtr<IWebInterfaceBrowserWindow>& BrowserWindowPtr ) +{ +#if UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG + if ( !BrowserWindowPtr.IsValid() ) + return false; + + TSharedPtr<IWebInterfaceBrowserWindow> WebBrowserWindow = BrowserWindowPtr.Pin(); + if ( !WebBrowserWindow.IsValid() ) + return false; + + if ( WebBrowserWindow->IsClosing() ) + { + const TWeakPtr<SWindow>* FoundWebBrowserWindow = BrowserWindowWidgets.Find( WebBrowserWindow ); + if ( FoundWebBrowserWindow != nullptr ) + { + TSharedPtr<SWindow> FoundWindow = FoundWebBrowserWindow->Pin(); + if ( FoundWindow.IsValid() ) + FoundWindow->RequestDestroyWindow(); + + BrowserWindowWidgets.Remove( WebBrowserWindow ); + return true; + } + } + else + WebBrowserWindow->CloseBrowser( false ); + + return false; +#else + return false; +#endif +} + +bool SWebInterface::HasMouseTransparency() const +{ + return bMouseTransparency && !bVirtualPointerTransparency; +} + +bool SWebInterface::HasVirtualPointerTransparency() const +{ + return bVirtualPointerTransparency; +} + +float SWebInterface::GetTransparencyDelay() const +{ + return TransparencyDelay; +} + +float SWebInterface::GetTransparencyThreshold() const +{ + return TransparencyThreshold; +} + +float SWebInterface::GetTransparencyTick() const +{ + return TransparencyTick; +} + +int32 SWebInterface::GetTextureWidth() const +{ + if ( !BrowserWindow.IsValid() ) + return 0; + + FSlateShaderResource* Resource = BrowserWindow->GetTexture(); + if ( !Resource ) + return 0; + + return Resource->GetWidth(); +} + +int32 SWebInterface::GetTextureHeight() const +{ + if ( !BrowserWindow.IsValid() ) + return 0; + + FSlateShaderResource* Resource = BrowserWindow->GetTexture(); + if ( !Resource ) + return 0; + + return Resource->GetHeight(); +} + +FColor SWebInterface::ReadTexturePixel( int32 X, int32 Y ) const +{ + if ( X < 0 || X >= GetTextureWidth() ) + return FColor::Transparent; + if ( Y < 0 || Y >= GetTextureHeight() ) + return FColor::Transparent; + + // temporary transparency fix (array of pixels still broken for accelerated paint) + if ( bAcceleratedPaint ) + Y = GetTextureHeight() - Y - 1; + + TArray<FColor> Pixels = ReadTexturePixels( X, Y, 1, 1 ); + if ( Pixels.Num() > 0 ) + return Pixels[ 0 ]; + + return FColor::Transparent; +} + +TArray<FColor> SWebInterface::ReadTexturePixels( int32 X, int32 Y, int32 Width, int32 Height ) const +{ + TArray<FColor> OutPixels; + if ( !BrowserWindow.IsValid() ) + return OutPixels; + + FSlateShaderResource* Resource = BrowserWindow->GetTexture(); + if ( !Resource || Resource->GetType() != ESlateShaderResource::NativeTexture ) + return OutPixels; + + FTexture2DRHIRef TextureRHI; + TextureRHI = ( ( TSlateTexture<FTexture2DRHIRef>* )Resource )->GetTypedResource(); + + struct FReadSurfaceContext + { + FTexture2DRHIRef Texture; + TArray<FColor>* OutData; + FIntRect Rect; + FReadSurfaceDataFlags Flags; + }; + + int32 ResourceWidth = (int32)Resource->GetWidth(); + int32 ResourceHeight = (int32)Resource->GetHeight(); + + X = FMath::Clamp( X, 0, ResourceWidth - 1 ); + Y = FMath::Clamp( Y, 0, ResourceHeight - 1 ); + + Width = FMath::Clamp( Width, 1, ResourceWidth ); + Width = Width - FMath::Max( X + Width - ResourceWidth, 0 ); + + Height = FMath::Clamp( Height, 1, ResourceHeight ); + Height = Height - FMath::Max( Y + Height - ResourceHeight, 0 ); + + FReadSurfaceContext Context = + { + TextureRHI, + &OutPixels, + FIntRect( X, Y, X + Width, Y + Height ), + FReadSurfaceDataFlags() + }; + + ENQUEUE_RENDER_COMMAND( ReadSurfaceCommand )( + [ Context ]( FRHICommandListImmediate& RHICmdList ) + { + RHICmdList.ReadSurfaceData( + Context.Texture, + Context.Rect, + *Context.OutData, + Context.Flags + ); + } ); + FlushRenderingCommands(); + + return OutPixels; +} + +void SWebInterface::LoadURL( FString NewURL ) +{ + if ( BrowserView.IsValid() ) + BrowserView->LoadURL( NewURL ); +} + +void SWebInterface::LoadString( FString Contents, FString DummyURL ) +{ + if ( BrowserView.IsValid() ) + BrowserView->LoadString( Contents, DummyURL ); +} + +void SWebInterface::Reload() +{ + if ( BrowserView.IsValid() ) + BrowserView->Reload(); +} + +void SWebInterface::StopLoad() +{ + if ( BrowserView.IsValid() ) + BrowserView->StopLoad(); +} + +FString SWebInterface::GetUrl() const +{ + if ( BrowserView.IsValid() ) + return BrowserView->GetUrl(); + + return FString(); +} + +bool SWebInterface::IsLoaded() const +{ + if ( BrowserView.IsValid() ) + return BrowserView->IsLoaded(); + + return false; +} + +bool SWebInterface::IsLoading() const +{ + if ( BrowserView.IsValid() ) + return BrowserView->IsLoading(); + + return false; +} + +void SWebInterface::ExecuteJavascript( const FString& ScriptText ) +{ + if ( BrowserView.IsValid() ) + BrowserView->ExecuteJavascript( ScriptText ); +} + +void SWebInterface::BindUObject( const FString& Name, UObject* Object, bool bIsPermanent ) +{ + if ( BrowserView.IsValid() ) + BrowserView->BindUObject( Name, Object, bIsPermanent ); +} + +void SWebInterface::UnbindUObject( const FString& Name, UObject* Object, bool bIsPermanent ) +{ + if ( BrowserView.IsValid() ) + BrowserView->UnbindUObject( Name, Object, bIsPermanent ); +} + +void SWebInterface::BindAdapter( const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter ) +{ + if ( BrowserView.IsValid() ) + BrowserView->BindAdapter( Adapter ); +} + +void SWebInterface::UnbindAdapter( const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter ) +{ + if ( BrowserView.IsValid() ) + BrowserView->UnbindAdapter( Adapter ); +} + +void SWebInterface::BindInputMethodSystem( ITextInputMethodSystem* TextInputMethodSystem ) +{ + if ( BrowserView.IsValid() ) + BrowserView->BindInputMethodSystem( TextInputMethodSystem ); +} + +void SWebInterface::UnbindInputMethodSystem() +{ + if ( BrowserView.IsValid() ) + BrowserView->UnbindInputMethodSystem(); +} + +void SWebInterface::SetParentWindow( TSharedPtr<SWindow> Window ) +{ + if ( BrowserView.IsValid() ) + BrowserView->SetParentWindow( Window ); +} +#endif diff --git a/Plugins/WebUI/Source/WebUI/Private/WebInterface.cpp b/Plugins/WebUI/Source/WebUI/Private/WebInterface.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0308d9a2c5972b3d6ef6bd0e1694e6a342fb2b91 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/WebInterface.cpp @@ -0,0 +1,615 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "WebInterface.h" +#include "WebInterfaceObject.h" +#include "PlatformHttp.h" +#include "Engine/GameInstance.h" +#include "Engine/GameViewportClient.h" +#include "Engine/LocalPlayer.h" +#include "Engine/World.h" +#include "Framework/Application/SlateApplication.h" +#include "Misc/FileHelper.h" +#include "UObject/ConstructorHelpers.h" +#include "Widgets/SViewport.h" +#include "Widgets/SWidget.h" +#include "Widgets/Layout/SBox.h" +#include "Widgets/Text/STextBlock.h" + +#if !UE_SERVER +#include "SWebInterface.h" +#endif + +#if WITH_EDITOR +#include "Materials/MaterialInterface.h" +#include "Materials/MaterialExpressionMaterialFunctionCall.h" +#include "Materials/MaterialExpressionTextureSample.h" +#include "Materials/MaterialExpressionTextureSampleParameter2D.h" +#include "Materials/MaterialFunction.h" +#include "Materials/Material.h" +#include "Factories/MaterialFactoryNew.h" +#include "AssetRegistry/AssetRegistryModule.h" +#include "PackageHelperFunctions.h" +#endif + +#if WITH_EDITOR || PLATFORM_ANDROID +#include "WebBrowserTexture.h" +#endif + +#define LOCTEXT_NAMESPACE "WebInterface" + +UWebInterface::UWebInterface( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + bIsVariable = true; + FrameRate = 60; + + SetVisibility( ESlateVisibility::SelfHitTestInvisible ); + + bEnableMouseTransparency = false; + MouseTransparencyThreshold = 0.333f; + MouseTransparencyDelay = 0.1f; + MouseTransparencyTick = 0.05f; + + bEnableVirtualPointerTransparency = false; + VirtualPointerTransparencyThreshold = 0.333f; + + bAcceleratedPaint = true; + bCustomCursors = false; + +#if WITH_EDITOR || PLATFORM_ANDROID + struct FConstructorStatics + { + ConstructorHelpers::FObjectFinder<UObject> DefaultTextureMaterial; + FConstructorStatics() + : DefaultTextureMaterial( TEXT( "/WebBrowserWidget/WebTexture_M" ) ) + { + // + } + }; + static FConstructorStatics ConstructorStatics; + + UWebBrowserTexture::StaticClass();// hard reference + DefaultMaterial = (UMaterial*)ConstructorStatics.DefaultTextureMaterial.Object; +#endif +} + +bool UWebInterface::Load( const FString& File ) +{ + static FString Scheme = "pak"; + if ( File.Len() <= 0 ) + return false; + + FString URL = Scheme; + if ( !URL.EndsWith( "://" ) ) + URL += "://"; + + FString FilePath = File; + FilePath = FilePath.Replace( TEXT( "\\" ), TEXT( "/" ) ); + FilePath = FilePath.Replace( TEXT( "//" ), TEXT( "/" ) ); + + URL += FilePath; +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + WebInterfaceWidget->LoadURL( URL ); +#endif + + FilePath = FPaths::ProjectContentDir() + File; + FilePath = FilePath.Replace( TEXT( "\\" ), TEXT( "/" ) ); + FilePath = FilePath.Replace( TEXT( "//" ), TEXT( "/" ) ); + + const int64 FileSize = IFileManager::Get().FileSize( *FilePath ); + return FileSize != INDEX_NONE; +} + +void UWebInterface::LoadHTML( const FString& HTML ) +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + WebInterfaceWidget->LoadString( HTML, "http://localhost" ); +#endif +} + +void UWebInterface::LoadURL( const FString& URL ) +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + WebInterfaceWidget->LoadURL( URL ); +#endif +} + +void UWebInterface::LoadFile( const FString& File, EWebInterfaceDirectory Directory /*= EWebInterfaceDirectory::UI*/ ) +{ +#if PLATFORM_ANDROID + extern FString GFilePathBase; + FString ProjName = !FApp::IsProjectNameEmpty() ? FApp::GetProjectName() : FPlatformProcess::ExecutableName(); + FString BasePath = GFilePathBase + TEXT( "/UE4Game/" ) + ProjName + TEXT( "/" ); + FString FilePath = Directory == EWebInterfaceDirectory::Content ? + BasePath + ProjName + TEXT( "/Content/" ) + File : + BasePath + ProjName + TEXT( "/UI/" ) + File; +#elif PLATFORM_IOS + FString FilePath = Directory == EWebInterfaceDirectory::Content ? + IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead( *FPaths::ProjectContentDir() ) + File : + IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead( *FPaths::ProjectDir() ) + TEXT( "UI/" ) + File; +#else + FString FilePath = Directory == EWebInterfaceDirectory::Content ? + FPaths::ConvertRelativePathToFull( FPaths::ProjectContentDir() ) + File : + FPaths::ConvertRelativePathToFull( FPaths::ProjectDir() ) + TEXT( "UI/" ) + File; +#endif + + FilePath = FilePath.Replace( TEXT( "\\" ), TEXT( "/" ) ); + FilePath = FilePath.Replace( TEXT( "//" ), TEXT( "/" ) ); + + LoadURL( TEXT( "file:///" ) + FilePath ); +} + +bool UWebInterface::LoadContent( const FString& File, bool bScript /*= false*/ ) +{ + FString FilePath = FPaths::ProjectContentDir() + File; + FilePath = FilePath.Replace( TEXT( "\\" ), TEXT( "/" ) ); + FilePath = FilePath.Replace( TEXT( "//" ), TEXT( "/" ) ); + + FString Text; + if ( !FFileHelper::LoadFileToString( Text, *FilePath ) ) + return false; + + if ( bScript ) + Execute( Text ); + else + LoadHTML( Text ); + + return true; +} + +FString UWebInterface::GetURL() const +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->GetUrl(); +#endif + return FString(); +} + +void UWebInterface::Execute( const FString& Script ) +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + WebInterfaceWidget->ExecuteJavascript( Script ); +#endif +} + +void UWebInterface::Call( const FString& Function, const FJsonLibraryValue& Data ) +{ + // reserved + if ( Function == "broadcast" ) + return; + +#if !UE_SERVER + if ( !WebInterfaceWidget.IsValid() ) + return; + + if ( Data.GetType() != EJsonLibraryType::Invalid ) + WebInterfaceWidget->ExecuteJavascript( FString::Printf( TEXT( "ue.interface[%s](%s)" ), + *FJsonLibraryValue( Function ).Stringify(), + *Data.Stringify() ) ); + else + WebInterfaceWidget->ExecuteJavascript( FString::Printf( TEXT( "ue.interface[%s]()" ), + *FJsonLibraryValue( Function ).Stringify() ) ); +#endif +} + +void UWebInterface::Bind( const FString& Name, UObject* Object ) +{ + if ( !Object ) + return; + + // reserved + if ( Name.ToLower() == "interface" ) + return; + +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + WebInterfaceWidget->BindUObject( Name, Object ); +#endif +} + +void UWebInterface::Unbind( const FString& Name, UObject* Object ) +{ + if ( !Object ) + return; + + // reserved + if ( Name.ToLower() == "interface" ) + return; + +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + WebInterfaceWidget->UnbindUObject( Name, Object ); +#endif +} + +void UWebInterface::EnableIME() +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + WebInterfaceWidget->BindInputMethodSystem( FSlateApplication::Get().GetTextInputMethodSystem() ); +#endif +} + +void UWebInterface::DisableIME() +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + WebInterfaceWidget->UnbindInputMethodSystem(); +#endif +} + +void FindChildWidgetsOfType( const FString& Type, TSharedRef<SWidget> Widget, TArray<TSharedRef<SWidget>>& Array ) +{ + FChildren* Children = Widget->GetChildren(); + if ( !Children ) + return; + + for ( int32 i = 0; i < Children->Num(); i++ ) + { + TSharedRef<SWidget> Child = Children->GetChildAt( i ); + if ( Type.IsEmpty() || Child->GetTypeAsString() == Type ) + Array.Add( Child ); + + FindChildWidgetsOfType( Type, Child, Array ); + } +} + +void UWebInterface::Focus( EMouseLockMode MouseLockMode /*= EMouseLockMode::LockOnCapture*/ ) +{ + SetVisibility( ESlateVisibility::SelfHitTestInvisible ); + +#if !UE_SERVER + UWorld* World = GetWorld(); + if ( !World ) + return; + + UGameViewportClient* GameViewport = World->GetGameViewport(); + UGameInstance* GameInstance = World->GetGameInstance(); + if ( GameViewport ) + { + TSharedPtr<SWidget> BrowserWidget = WebInterfaceWidget; + if ( BrowserWidget.IsValid() ) + { + TSharedPtr<SViewport> ViewportWidget = GameViewport->GetGameViewportWidget(); + if ( GameInstance && ViewportWidget.IsValid() ) + { + TSharedRef<SWidget> BrowserWidgetRef = BrowserWidget.ToSharedRef(); + TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef(); + + TArray<TSharedRef<SWidget>> Children; + FindChildWidgetsOfType( "SViewport", BrowserWidgetRef, Children ); + if ( Children.Num() == 1 ) + { + BrowserWidgetRef = Children[ 0 ]; + BrowserWidget = BrowserWidgetRef; + } + + bool bLockMouseToViewport = MouseLockMode == EMouseLockMode::LockAlways + || ( MouseLockMode == EMouseLockMode::LockInFullscreen && GameViewport->IsExclusiveFullscreenViewport() ); + + for ( int32 i = 0; i < GameInstance->GetNumLocalPlayers(); i++ ) + { + ULocalPlayer* LocalPlayer = GameInstance->GetLocalPlayerByIndex( i ); + if ( !LocalPlayer ) + continue; + + FReply& SlateOperations = LocalPlayer->GetSlateOperations(); + SlateOperations.SetUserFocus( BrowserWidgetRef ); + + if ( bLockMouseToViewport ) + SlateOperations.LockMouseToWidget( ViewportWidgetRef ); + else + SlateOperations.ReleaseMouseLock(); + + SlateOperations.ReleaseMouseCapture(); + } + } + + FSlateApplication::Get().SetAllUserFocus( BrowserWidget, EFocusCause::SetDirectly ); + FSlateApplication::Get().SetKeyboardFocus( BrowserWidget, EFocusCause::SetDirectly ); + } + + GameViewport->SetMouseLockMode( MouseLockMode ); + GameViewport->SetIgnoreInput( true ); + GameViewport->SetMouseCaptureMode( EMouseCaptureMode::NoCapture ); + } +#endif +} + +void UWebInterface::Unfocus( EMouseCaptureMode MouseCaptureMode /*= EMouseCaptureMode::CapturePermanently*/ ) +{ + SetVisibility( ESlateVisibility::HitTestInvisible ); + +#if !UE_SERVER + UWorld* World = GetWorld(); + if ( !World ) + return; + + UGameViewportClient* GameViewport = World->GetGameViewport(); + UGameInstance* GameInstance = World->GetGameInstance(); + if ( GameViewport ) + { + FSlateApplication::Get().ClearKeyboardFocus( EFocusCause::SetDirectly ); + FSlateApplication::Get().SetAllUserFocusToGameViewport(); + + TSharedPtr<SViewport> ViewportWidget = GameViewport->GetGameViewportWidget(); + if ( GameInstance && ViewportWidget.IsValid() ) + { + TSharedRef<SViewport> ViewportWidgetRef = ViewportWidget.ToSharedRef(); + for ( int32 i = 0; i < GameInstance->GetNumLocalPlayers(); i++ ) + { + ULocalPlayer* LocalPlayer = GameInstance->GetLocalPlayerByIndex( i ); + if ( !LocalPlayer ) + continue; + + FReply& SlateOperations = LocalPlayer->GetSlateOperations(); + SlateOperations.UseHighPrecisionMouseMovement( ViewportWidgetRef ); + SlateOperations.SetUserFocus( ViewportWidgetRef ); + SlateOperations.LockMouseToWidget( ViewportWidgetRef ); + } + } + + GameViewport->SetMouseLockMode( EMouseLockMode::LockOnCapture ); + GameViewport->SetIgnoreInput( false ); + GameViewport->SetMouseCaptureMode( MouseCaptureMode ); + } +#endif +} + +void UWebInterface::ResetMousePosition() +{ + UWorld* World = GetWorld(); + if ( !World ) + return; + + UGameViewportClient* GameViewport = World->GetGameViewport(); + if ( GameViewport && GameViewport->Viewport ) + { + FIntPoint SizeXY = GameViewport->Viewport->GetSizeXY(); + GameViewport->Viewport->SetMouse( SizeXY.X / 2, SizeXY.Y / 2 ); + } +} + +bool UWebInterface::IsUsingAcceleratedPaint() const +{ + return bAcceleratedPaint; +} + +bool UWebInterface::IsMouseTransparencyEnabled() const +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->HasMouseTransparency(); +#endif + return false; +} + +bool UWebInterface::IsVirtualPointerTransparencyEnabled() const +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->HasVirtualPointerTransparency(); +#endif + return false; +} + +float UWebInterface::GetTransparencyDelay() const +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->GetTransparencyDelay(); +#endif + return 0.0f; +} + +float UWebInterface::GetTransparencyThreshold() const +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->GetTransparencyThreshold(); +#endif + return 0.0f; +} + +float UWebInterface::GetTransparencyTick() const +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->GetTransparencyTick(); +#endif + return 0.0f; +} + +int32 UWebInterface::GetTextureWidth() const +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->GetTextureWidth(); +#endif + return 0; +} + +int32 UWebInterface::GetTextureHeight() const +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->GetTextureHeight(); +#endif + return 0; +} + +FColor UWebInterface::ReadTexturePixel( int32 X, int32 Y ) +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->ReadTexturePixel( X, Y ); +#endif + return FColor::Transparent; +} + +TArray<FColor> UWebInterface::ReadTexturePixels( int32 X, int32 Y, int32 Width, int32 Height ) +{ +#if !UE_SERVER + if ( WebInterfaceWidget.IsValid() ) + return WebInterfaceWidget->ReadTexturePixels( X, Y, Width, Height ); +#endif + return TArray<FColor>(); +} + +void UWebInterface::ReleaseSlateResources( bool bReleaseChildren ) +{ + Super::ReleaseSlateResources( bReleaseChildren ); +#if !UE_SERVER + WebInterfaceWidget.Reset(); +#endif +} + +#if PLATFORM_WINDOWS +#include "Windows/AllowWindowsPlatformTypes.h" +bool _MultiGPU() +{ + DISPLAY_DEVICE DisplayDevice; + DisplayDevice.cb = sizeof( DisplayDevice ); + DWORD DeviceIndex = 0; + + TMap<FString, int32> DeviceDisplays; + while( EnumDisplayDevices( 0, DeviceIndex, &DisplayDevice, 0 ) ) + { + FString DeviceID = DisplayDevice.DeviceID; + if ( DeviceDisplays.Contains( DeviceID ) ) + DeviceDisplays[ DeviceID ]++; + else + DeviceDisplays.Add( DeviceID, 1 ); + + FMemory::Memzero( DisplayDevice ); + DisplayDevice.cb = sizeof( DisplayDevice ); + DeviceIndex++; + } + + return DeviceDisplays.Num() > 1; +} +#include "Windows/HideWindowsPlatformTypes.h" +#endif + +TSharedRef<SWidget> UWebInterface::RebuildWidget() +{ +#if !UE_SERVER + if ( IsDesignTime() ) + return SNew( SBox ) + .HAlign( HAlign_Center ) + .VAlign( VAlign_Center ) + [ + SNew( STextBlock ) + .Text( LOCTEXT( "Web UI", "Web UI" ) ) + ]; + +#if PLATFORM_WINDOWS + if ( bAcceleratedPaint && _MultiGPU() ) + bAcceleratedPaint = false; +#else + bAcceleratedPaint = false; +#endif + + WebInterfaceWidget = SNew( SWebInterface ) + .FrameRate( FrameRate ) + .InitialURL( InitialURL ) + .AcceleratedPaint( bAcceleratedPaint ) + .NativeCursors( !bCustomCursors ) + .EnableMouseTransparency( bEnableMouseTransparency ) + .EnableVirtualPointerTransparency( bEnableVirtualPointerTransparency ) + .TransparencyDelay( MouseTransparencyDelay ) + .TransparencyTick( MouseTransparencyTick ) + .TransparencyThreshold( bEnableVirtualPointerTransparency ? + VirtualPointerTransparencyThreshold : + MouseTransparencyThreshold ) + .OnUrlChanged( BIND_UOBJECT_DELEGATE( FOnTextChanged, HandleUrlChanged ) ) + .OnBeforePopup( BIND_UOBJECT_DELEGATE( FOnBeforePopupDelegate, HandleBeforePopup ) ); + +#if WITH_CEF3 + MyObject = NewObject<UWebInterfaceObject>(); + if ( MyObject ) + { + MyObject->MyInterface = this; + WebInterfaceWidget->BindUObject( "interface", MyObject ); + } +#endif + + return WebInterfaceWidget.ToSharedRef(); +#else + TSharedPtr<SBox> WebInterfaceWidget = SNew( SBox ); + return WebInterfaceWidget.ToSharedRef(); +#endif +} + +void UWebInterface::HandleUrlChanged( const FText& URL ) +{ + FString Hash = URL.ToString(); + + int32 Index = Hash.Find( "#" ); + if ( Index >= 0 ) + Hash = Hash.RightChop( Index + 1 ); + + if ( ( Hash.StartsWith( "[" ) && Hash.EndsWith( "]" ) ) || ( Hash.StartsWith( "%5B" ) && Hash.EndsWith( "%5D" ) ) ) + { + FString JSON = FPlatformHttp::UrlDecode( Hash ); + + FJsonLibraryValue Value = FJsonLibraryValue::Parse( JSON ); + if ( Value.GetType() == EJsonLibraryType::Array ) + { + TArray<FJsonLibraryValue> Array = Value.ToArray(); + if ( Array.Num() == 2 || Array.Num() == 3 ) + { + FJsonLibraryValue Name = Array[ 0 ]; + FJsonLibraryValue Data = Array[ 1 ]; + if ( Name.GetType() == EJsonLibraryType::String ) + { + FName BroadcastName = *Name.GetString(); + if ( Array.Num() > 2 ) + { + FJsonLibraryValue Callback = Array[ 2 ]; + if ( Callback.GetType() == EJsonLibraryType::String ) + { + FString BroadcastCallback = Callback.GetString(); + if ( !BroadcastCallback.IsEmpty() ) + { + OnInterfaceEvent.Broadcast( BroadcastName, Data, FWebInterfaceCallback( this, BroadcastCallback ) ); + return; + } + } + } + + OnInterfaceEvent.Broadcast( BroadcastName, Data, FWebInterfaceCallback() ); + return; + } + } + + return; + } + } + + OnUrlChangedEvent.Broadcast( URL ); +} + +bool UWebInterface::HandleBeforePopup( FString URL, FString Frame ) +{ + OnPopupEvent.Broadcast( URL, Frame ); + return true; +} + +#if WITH_EDITOR +const FText UWebInterface::GetPaletteCategory() +{ + return LOCTEXT( "Common", "Common" ); +} +#endif + +UMaterial* UWebInterface::GetDefaultMaterial() const +{ + return DefaultMaterial; +} + +#undef LOCTEXT_NAMESPACE diff --git a/Plugins/WebUI/Source/WebUI/Private/WebInterfaceAssetManager.cpp b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceAssetManager.cpp new file mode 100644 index 0000000000000000000000000000000000000000..db2caa50d0691fb12606d8e62dc162d849093bf2 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceAssetManager.cpp @@ -0,0 +1,32 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "WebInterfaceAssetManager.h" + +#if WITH_EDITOR || PLATFORM_ANDROID || PLATFORM_IOS +#include "WebBrowserTexture.h" +#endif + +UWebInterfaceAssetManager::UWebInterfaceAssetManager(const FObjectInitializer& ObjectInitializer) + : Super(ObjectInitializer) , + DefaultMaterial(FString(TEXT("/WebBrowserWidget/WebTexture_M.WebTexture_M"))) , + DefaultTranslucentMaterial(FString(TEXT("/WebBrowserWidget/WebTexture_TM.WebTexture_TM"))) +{ +#if WITH_EDITOR || PLATFORM_ANDROID || PLATFORM_IOS + UWebBrowserTexture::StaticClass(); +#endif +}; + +void UWebInterfaceAssetManager::LoadDefaultMaterials() +{ + DefaultMaterial.LoadSynchronous(); + DefaultTranslucentMaterial.LoadSynchronous(); +} + +UMaterial* UWebInterfaceAssetManager::GetDefaultMaterial() +{ + return DefaultMaterial.Get(); +} + +UMaterial* UWebInterfaceAssetManager::GetDefaultTranslucentMaterial() +{ + return DefaultTranslucentMaterial.Get(); +} diff --git a/Plugins/WebUI/Source/WebUI/Private/WebInterfaceCallback.cpp b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceCallback.cpp new file mode 100644 index 0000000000000000000000000000000000000000..450e7fbe644157eb24dd63894f7c16e45ef33362 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceCallback.cpp @@ -0,0 +1,36 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "WebInterfaceCallback.h" +#include "WebInterface.h" + +FWebInterfaceCallback::FWebInterfaceCallback() +{ + // +} + +FWebInterfaceCallback::FWebInterfaceCallback( TWeakObjectPtr<UWebInterface> Interface, const FString& Callback ) +{ + MyInterface = Interface; + MyCallback = Callback; +} + +bool FWebInterfaceCallback::IsValid() const +{ + if ( MyInterface.IsValid() ) + return !MyCallback.IsEmpty(); + + return false; +} + +void FWebInterfaceCallback::Call( const FJsonLibraryValue& Data ) +{ + if ( !MyInterface.IsValid() || MyCallback.IsEmpty() ) + return; + + if ( Data.GetType() != EJsonLibraryType::Invalid ) + MyInterface->Execute( FString::Printf( TEXT( "ue.interface[%s](%s)" ), + *FJsonLibraryValue( MyCallback ).Stringify(), + *Data.Stringify() ) ); + else + MyInterface->Execute( FString::Printf( TEXT( "ue.interface[%s]()" ), + *FJsonLibraryValue( MyCallback ).Stringify() ) ); +} diff --git a/Plugins/WebUI/Source/WebUI/Private/WebInterfaceHelpers.cpp b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceHelpers.cpp new file mode 100644 index 0000000000000000000000000000000000000000..45f3b7839cfd7a40c8a82f8f13f6313d670a4377 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceHelpers.cpp @@ -0,0 +1,395 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "WebInterfaceHelpers.h" +#include "IImageWrapper.h" +#include "IImageWrapperModule.h" +#include "ImagePixelData.h" +#include "Kismet/KismetRenderingLibrary.h" + +#include "CanvasTypes.h" +#include "Engine/Canvas.h" +#include "Engine/Texture2D.h" +#include "Engine/TextureRenderTarget2D.h" +#include "EngineModule.h" +#include "LegacyScreenPercentageDriver.h" +#include "Rendering/Texture2DResource.h" +#include "RenderUtils.h" + +#if WITH_EDITOR +#include "TextureCompiler.h" +#endif + +bool UWebInterfaceHelpers::WebInterfaceCallback_IsValid( const FWebInterfaceCallback& Target ) +{ + return Target.IsValid(); +} + +void UWebInterfaceHelpers::WebInterfaceCallback_Call( FWebInterfaceCallback& Target, const FJsonLibraryValue& Data ) +{ + Target.Call( Data ); +} + + +// FImageUtils::ImageResize() +TArray64<FColor> UWebInterfaceHelpers::ResizeImage( int32 SrcWidth, int32 SrcHeight, const TArray64<FColor>& SrcData, int32 DstWidth, int32 DstHeight, bool bLinearSpace, bool bForceOpaqueOutput ) +{ + TArray64<FColor> DstData; + DstData.Empty( DstWidth * DstHeight ); + DstData.AddZeroed( DstWidth * DstHeight ); + + check( SrcData.Num() >= SrcWidth * SrcHeight ); + check( DstData.Num() >= DstWidth * DstHeight ); + + float SrcX = 0; + float SrcY = 0; + + const float StepSizeX = SrcWidth / (float)DstWidth; + const float StepSizeY = SrcHeight / (float)DstHeight; + + for ( int32 Y = 0; Y < DstHeight; Y++ ) + { + int32 PixelPos = Y * DstWidth; + SrcX = 0.0f; + + for ( int32 X = 0; X < DstWidth; X++ ) + { + int32 PixelCount = 0; + float EndX = SrcX + StepSizeX; + float EndY = SrcY + StepSizeY; + + int32 PosY = FMath::TruncToInt( SrcY + 0.5f ); + PosY = FMath::Clamp<int32>( PosY, 0, ( SrcHeight - 1 ) ); + + int32 PosX = FMath::TruncToInt( SrcX + 0.5f ); + PosX = FMath::Clamp<int32>( PosX, 0, ( SrcWidth - 1 ) ); + + int32 EndPosY = FMath::TruncToInt( EndY + 0.5f ); + EndPosY = FMath::Clamp<int32>( EndPosY, 0, ( SrcHeight - 1 ) ); + + int32 EndPosX = FMath::TruncToInt( EndX + 0.5f ); + EndPosX = FMath::Clamp<int32>( EndPosX, 0, ( SrcWidth - 1 ) ); + + FColor FinalColor; + if ( bLinearSpace ) + { + FLinearColor LinearStepColor( 0.0f, 0.0f, 0.0f, 0.0f ); + for ( int32 PixelX = PosX; PixelX <= EndPosX; PixelX++ ) + { + for ( int32 PixelY = PosY; PixelY <= EndPosY; PixelY++ ) + { + int32 StartPixel = PixelX + PixelY * SrcWidth; + + LinearStepColor += SrcData[StartPixel]; + PixelCount++; + } + } + + LinearStepColor /= (float)PixelCount; + FinalColor = LinearStepColor.ToFColor( true ); + } + else + { + FVector4 StepColor( 0, 0, 0, 0 ); + for ( int32 PixelX = PosX; PixelX <= EndPosX; PixelX++ ) + { + for ( int32 PixelY = PosY; PixelY <= EndPosY; PixelY++ ) + { + int32 StartPixel = PixelX + PixelY * SrcWidth; + StepColor.X += (float)SrcData[StartPixel].R; + StepColor.Y += (float)SrcData[StartPixel].G; + StepColor.Z += (float)SrcData[StartPixel].B; + StepColor.W += (float)SrcData[StartPixel].A; + + PixelCount++; + } + } + uint8 FinalR = FMath::Clamp( FMath::TruncToInt( StepColor.X / (float)PixelCount ), 0, 255 ); + uint8 FinalG = FMath::Clamp( FMath::TruncToInt( StepColor.Y / (float)PixelCount ), 0, 255 ); + uint8 FinalB = FMath::Clamp( FMath::TruncToInt( StepColor.Z / (float)PixelCount ), 0, 255 ); + uint8 FinalA = FMath::Clamp( FMath::TruncToInt( StepColor.W / (float)PixelCount ), 0, 255 ); + + FinalColor = FColor( FinalR, FinalG, FinalB, FinalA ); + } + + if ( bForceOpaqueOutput ) + FinalColor.A = 255; + + DstData[PixelPos] = FinalColor; + + SrcX = EndX; + PixelPos++; + } + + SrcY += StepSizeY; + } + + return DstData; +} + + +struct FGlobalInterfaceImageWrappers +{ + IImageWrapper* FindOrCreateImageWrapper( EImageFormat InFormat ) + { + FScopeLock ScopeLock( &ImageWrappersCriticalSection ); + for ( int32 Index = 0; Index < AvailableImageWrappers.Num(); ++Index ) + { + if ( AvailableImageWrappers[Index].Get<0>() == InFormat ) + { + IImageWrapper* Wrapper = AvailableImageWrappers[Index].Get<1>(); + AvailableImageWrappers.RemoveAtSwap( Index, 1, false ); + return Wrapper; + } + } + + IImageWrapperModule* ImageWrapperModule = FModuleManager::GetModulePtr<IImageWrapperModule>( "ImageWrapper" ); + if ( !ensure( ImageWrapperModule ) ) + return nullptr; + + TSharedPtr<IImageWrapper> NewImageWrapper = ImageWrapperModule->CreateImageWrapper( InFormat ); + if ( !ensure( NewImageWrapper.IsValid() ) ) + return nullptr; + + AllImageWrappers.Add( MakeTuple( InFormat, NewImageWrapper.ToSharedRef() ) ); + return NewImageWrapper.Get(); + } + + void ReturnImageWrapper( IImageWrapper* InWrapper ) + { + FScopeLock ScopeLock( &ImageWrappersCriticalSection ); + for ( const TTuple<EImageFormat, TSharedRef<IImageWrapper>>& Pair : AllImageWrappers ) + { + if ( &Pair.Get<1>().Get() == InWrapper ) + { + AvailableImageWrappers.Add( MakeTuple( Pair.Get<0>(), InWrapper ) ); + return; + } + } + } + +private: + FCriticalSection ImageWrappersCriticalSection; + TArray< TTuple<EImageFormat, IImageWrapper*> > AvailableImageWrappers; + TArray< TTuple<EImageFormat, TSharedRef<IImageWrapper>> > AllImageWrappers; +} GInterfaceImageWrappers; + + +IImageWrapper* UWebInterfaceHelpers::FindOrCreateImageWrapper( EImageFormat Format ) +{ + return GInterfaceImageWrappers.FindOrCreateImageWrapper( Format ); +} + +bool UWebInterfaceHelpers::SetImageWrapper( IImageWrapper* Wrapper, const TArray64<FColor>& Pixels, FIntPoint Size ) +{ + if ( !Wrapper ) + return false; + + return Wrapper->SetRaw( Pixels.GetData(), Pixels.GetAllocatedSize(), Size.X, Size.Y, TImagePixelDataTraits<FColor>::PixelLayout, TImagePixelDataTraits<FColor>::BitDepth ); +} + +void UWebInterfaceHelpers::ReturnImageWrapper( IImageWrapper* Wrapper ) +{ + GInterfaceImageWrappers.ReturnImageWrapper( Wrapper ); +} + +FIntPoint UWebInterfaceHelpers::GenerateImageFromRenderTarget( TArray64<FColor>& OutPixels, UTextureRenderTarget2D* RenderTarget ) +{ + if ( !RenderTarget ) + return FIntPoint::ZeroValue; + + FTextureRenderTargetResource* RenderTargetResource = RenderTarget->GameThread_GetRenderTargetResource(); + + int32 TargetWidth = RenderTargetResource->GetSizeX(); + int32 TargetHeight = RenderTargetResource->GetSizeY(); + + OutPixels.SetNumUninitialized( TargetWidth * TargetHeight ); + if ( !RenderTargetResource->ReadPixelsPtr( OutPixels.GetData(), FReadSurfaceDataFlags(), FIntRect( 0, 0, TargetWidth, TargetHeight ) ) ) + return FIntPoint::ZeroValue; + + return FIntPoint( TargetWidth, TargetHeight ); +} + +// ThumbnailGenerator::GenerateThumbnailFromTexture() +FIntPoint UWebInterfaceHelpers::GenerateImageFromTexture( TArray64<FColor>& OutPixels, UTexture2D* Texture ) +{ + if ( !Texture ) + return FIntPoint::ZeroValue; + +#if WITH_EDITOR + FTextureCompilingManager::Get().FinishCompilation( { Texture } ); +#endif + + Texture->SetForceMipLevelsToBeResident( 30.0f ); + Texture->WaitForStreaming(); + + int32 TargetWidth = Texture->GetSizeX(); + int32 TargetHeight = Texture->GetSizeY(); + + if ( TargetWidth == 0 || TargetHeight == 0 || !Texture->GetResource() ) + return FIntPoint::ZeroValue; + + UTextureRenderTarget2D* RenderTargetTexture = NewObject<UTextureRenderTarget2D>(); + RenderTargetTexture->AddToRoot(); + RenderTargetTexture->ClearColor = FLinearColor::Transparent; + RenderTargetTexture->TargetGamma = 0.f; + RenderTargetTexture->InitCustomFormat( TargetWidth, TargetHeight, PF_B8G8R8A8, false ); + + FTextureRenderTargetResource* RenderTargetResource = RenderTargetTexture->GameThread_GetRenderTargetResource(); + + FCanvas Canvas( RenderTargetResource, NULL, FGameTime::GetTimeSinceAppStart(), GWorld->Scene->GetFeatureLevel() ); + Canvas.Clear( FLinearColor::Black ); + + const bool bAlphaBlend = false; + Canvas.DrawTile( + 0.0f, + 0.0f, + (float)TargetWidth, + (float)TargetHeight, + 0.0f, + 0.0f, + 1.0f, + 1.0f, + FLinearColor::White, + Texture->GetResource(), + bAlphaBlend ); + Canvas.Flush_GameThread(); + + OutPixels.SetNumUninitialized( TargetWidth * TargetHeight ); + if ( !RenderTargetResource->ReadPixelsPtr( OutPixels.GetData(), FReadSurfaceDataFlags(), FIntRect( 0, 0, TargetWidth, TargetHeight ) ) ) + return FIntPoint::ZeroValue; + + RenderTargetTexture->RemoveFromRoot(); + RenderTargetTexture = nullptr; + + return FIntPoint( TargetWidth, TargetHeight ); +} + +// UCanvas::K2_DrawMaterial() +FIntPoint UWebInterfaceHelpers::GenerateImageFromMaterial( TArray64<FColor>& OutPixels, UMaterialInterface* Material, int32 Width, int32 Height ) +{ + int32 TargetWidth = 1024; + int32 TargetHeight = 1024; + + if ( Width > 0 && Height > 0 ) + { + TargetWidth = Width; + TargetHeight = Height; + } + else if ( Width > 0 ) + { + TargetWidth = Width; + TargetHeight = Width; + } + else if ( Height > 0 ) + { + TargetWidth = Height; + TargetHeight = Height; + } + + if ( !Material ) + return FIntPoint::ZeroValue; + if ( !FApp::CanEverRender() ) + return FIntPoint::ZeroValue; + + UWorld* World = + #if WITH_EDITOR + GIsEditor ? GWorld : + #endif + ( GEngine->GetWorldContexts().Num() > 0 ? GEngine->GetWorldContexts()[0].World() : nullptr ); + if ( !World ) + return FIntPoint::ZeroValue; + + World->FlushDeferredParameterCollectionInstanceUpdates(); + + UTextureRenderTarget2D* RenderTargetTexture = NewObject<UTextureRenderTarget2D>(); + RenderTargetTexture->AddToRoot(); + RenderTargetTexture->ClearColor = FLinearColor::Transparent; + RenderTargetTexture->TargetGamma = 0.f; + RenderTargetTexture->InitCustomFormat( TargetWidth, TargetHeight, PF_B8G8R8A8, false ); + + FTextureRenderTargetResource* RenderTargetResource = RenderTargetTexture->GameThread_GetRenderTargetResource(); + + UKismetRenderingLibrary::ClearRenderTarget2D( World, RenderTargetTexture, FLinearColor::Black ); + UKismetRenderingLibrary::DrawMaterialToRenderTarget( World, RenderTargetTexture, Material ); + + OutPixels.SetNumUninitialized( TargetWidth * TargetHeight ); + if ( !RenderTargetResource->ReadPixelsPtr( OutPixels.GetData(), FReadSurfaceDataFlags(), FIntRect( 0, 0, TargetWidth, TargetHeight ) ) ) + return FIntPoint::ZeroValue; + + // temporarily force opaque until fixed + for ( int32 i = 0; i < OutPixels.Num(); i++ ) + OutPixels[i].A = 255; + + RenderTargetTexture->RemoveFromRoot(); + RenderTargetTexture = nullptr; + + return FIntPoint( TargetWidth, TargetHeight ); +} + +// ThumbnailGenerator::GenerateThumbnailFromCamera() +bool UWebInterfaceHelpers::GenerateImageFromCamera( TArray64<FColor>& OutPixels, UObject* WorldContextObject, const FTransform& CameraTransform, int32 Width, int32 Height, float FOVDegrees, float MinZ, float Gamma ) +{ + if ( !WorldContextObject ) + return false; + + UWorld* World = WorldContextObject->GetWorld(); + if ( !World ) + return false; + + FSceneInterface* Scene = World->Scene; + OutPixels.SetNumUninitialized( Width * Height ); + + FIntPoint Size{ Width, Height }; + return GenerateImageFromScene( + OutPixels, + Scene, + CameraTransform.GetTranslation(), + FInverseRotationMatrix( CameraTransform.Rotator() ) * FInverseRotationMatrix( FRotator( 0, -90, 90 ) ), + FReversedZPerspectiveMatrix( FOVDegrees * 2, 1, 1, MinZ ), + Size, + Gamma ); +} + +// ThumbnailGeneratorImpl::RenderSceneToTexture() +bool UWebInterfaceHelpers::GenerateImageFromScene( TArray64<FColor>& OutPixels, FSceneInterface* Scene, const FVector& ViewOrigin, const FMatrix& ViewRotationMatrix, const FMatrix& ProjectionMatrix, FIntPoint TargetSize, float TargetGamma ) +{ + UTextureRenderTarget2D* RenderTargetTexture = NewObject<UTextureRenderTarget2D>(); + RenderTargetTexture->AddToRoot(); + RenderTargetTexture->ClearColor = FLinearColor::Transparent; + RenderTargetTexture->TargetGamma = TargetGamma; + RenderTargetTexture->InitCustomFormat( TargetSize.X, TargetSize.Y, PF_B8G8R8A8, false ); + + FTextureRenderTargetResource* RenderTargetResource = RenderTargetTexture->GameThread_GetRenderTargetResource(); + + FSceneViewFamilyContext ViewFamily( + FSceneViewFamily::ConstructionValues( RenderTargetResource, Scene, FEngineShowFlags( ESFIM_Game ) ) + .SetTime( FGameTime::GetTimeSinceAppStart() ) + ); + + ViewFamily.SetScreenPercentageInterface( new FLegacyScreenPercentageDriver( + ViewFamily, /* GlobalResolutionFraction = */ 1.0f ) ); + + FSceneViewInitOptions ViewInitOptions; + ViewInitOptions.SetViewRectangle( FIntRect( 0, 0, TargetSize.X, TargetSize.Y ) ); + ViewInitOptions.ViewFamily = &ViewFamily; + ViewInitOptions.ViewOrigin = ViewOrigin; + ViewInitOptions.ViewRotationMatrix = ViewRotationMatrix; + ViewInitOptions.ProjectionMatrix = ProjectionMatrix; + + FSceneView* NewView = new FSceneView( ViewInitOptions ); + ViewFamily.Views.Add( NewView ); + + FCanvas Canvas( RenderTargetResource, NULL, FGameTime::GetTimeSinceAppStart(), Scene->GetFeatureLevel() ); + Canvas.Clear( FLinearColor::Transparent ); + GetRendererModule().BeginRenderingViewFamily( &Canvas, &ViewFamily ); + + OutPixels.SetNumUninitialized( TargetSize.X * TargetSize.Y ); + if ( !RenderTargetResource->ReadPixelsPtr( OutPixels.GetData(), FReadSurfaceDataFlags(), FIntRect( 0, 0, TargetSize.X, TargetSize.Y ) ) ) + return false; + + FlushRenderingCommands(); + + RenderTargetTexture->RemoveFromRoot(); + RenderTargetTexture = nullptr; + + return true; +} diff --git a/Plugins/WebUI/Source/WebUI/Private/WebInterfaceInteractionComponent.cpp b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceInteractionComponent.cpp new file mode 100644 index 0000000000000000000000000000000000000000..54175ccca11117fa3bfca948b7ac22d0675d418c --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceInteractionComponent.cpp @@ -0,0 +1,210 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "WebInterfaceInteractionComponent.h" +#include "WebInterface.h" +#include "Blueprint/WidgetTree.h" +#include "Blueprint/UserWidget.h" +#include "Components/WidgetComponent.h" +#include "Kismet/GameplayStatics.h" + +UWebInterfaceInteractionComponent::UWebInterfaceInteractionComponent( const FObjectInitializer& ObjectInitializer ) + : Super( ObjectInitializer ) +{ + // +} + +UWebInterfaceInteractionComponent::FWidgetTraceResult UWebInterfaceInteractionComponent::PerformTrace() const +{ + FWidgetTraceResult TraceResult; + + bool bLineTrace = true; + TArray<UPrimitiveComponent*> IgnoredComponents; + + while ( bLineTrace ) + { + bLineTrace = false; + +// UWidgetInteractionComponent::PerformTrace() + TArray<FHitResult> MultiHits; + + FVector WorldDirection; + + switch( InteractionSource ) + { + case EWidgetInteractionSource::World: + { + const FVector WorldLocation = GetComponentLocation(); + const FTransform WorldTransform = GetComponentTransform(); + WorldDirection = WorldTransform.GetUnitAxis(EAxis::X); + + TArray<UPrimitiveComponent*> PrimitiveChildren; + GetRelatedComponentsToIgnoreInAutomaticHitTesting(PrimitiveChildren); + + FCollisionQueryParams Params = FCollisionQueryParams::DefaultQueryParam; + Params.AddIgnoredComponents(PrimitiveChildren); + Params.AddIgnoredComponents(IgnoredComponents); + + TraceResult.LineStartLocation = WorldLocation; + TraceResult.LineEndLocation = WorldLocation + (WorldDirection * InteractionDistance); + + GetWorld()->LineTraceMultiByChannel(MultiHits, TraceResult.LineStartLocation, TraceResult.LineEndLocation, TraceChannel, Params); + break; + } + case EWidgetInteractionSource::Mouse: + case EWidgetInteractionSource::CenterScreen: + { + TArray<UPrimitiveComponent*> PrimitiveChildren; + GetRelatedComponentsToIgnoreInAutomaticHitTesting(PrimitiveChildren); + + FCollisionQueryParams Params = FCollisionQueryParams::DefaultQueryParam; + Params.AddIgnoredComponents(PrimitiveChildren); + Params.AddIgnoredComponents(IgnoredComponents); + + APlayerController* PlayerController = GetWorld()->GetFirstPlayerController(); + ULocalPlayer* LocalPlayer = PlayerController->GetLocalPlayer(); + + if ( LocalPlayer && LocalPlayer->ViewportClient ) + { + if ( InteractionSource == EWidgetInteractionSource::Mouse ) + { + FVector2D MousePosition; + if ( LocalPlayer->ViewportClient->GetMousePosition(MousePosition) ) + { + FVector WorldOrigin; + if ( UGameplayStatics::DeprojectScreenToWorld(PlayerController, MousePosition, WorldOrigin, WorldDirection) == true ) + { + TraceResult.LineStartLocation = WorldOrigin; + TraceResult.LineEndLocation = WorldOrigin + WorldDirection * InteractionDistance; + + GetWorld()->LineTraceMultiByChannel(MultiHits, TraceResult.LineStartLocation, TraceResult.LineEndLocation, TraceChannel, Params); + } + } + } + else if ( InteractionSource == EWidgetInteractionSource::CenterScreen ) + { + FVector2D ViewportSize; + LocalPlayer->ViewportClient->GetViewportSize(ViewportSize); + + FVector WorldOrigin; + if ( UGameplayStatics::DeprojectScreenToWorld(PlayerController, ViewportSize * 0.5f, WorldOrigin, WorldDirection) == true ) + { + TraceResult.LineStartLocation = WorldOrigin; + TraceResult.LineEndLocation = WorldOrigin + WorldDirection * InteractionDistance; + + GetWorld()->LineTraceMultiByChannel(MultiHits, WorldOrigin, WorldOrigin + WorldDirection * InteractionDistance, TraceChannel, Params); + } + } + } + break; + } + case EWidgetInteractionSource::Custom: + { + const FTransform WorldTransform = GetComponentTransform(); + WorldDirection = WorldTransform.GetUnitAxis(EAxis::X); + TraceResult.HitResult = CustomHitResult; + TraceResult.bWasHit = CustomHitResult.bBlockingHit; + TraceResult.LineStartLocation = CustomHitResult.TraceStart; + TraceResult.LineEndLocation = CustomHitResult.TraceEnd; + break; + } + } + + if ( InteractionSource != EWidgetInteractionSource::Custom ) + { + for ( const FHitResult& HitResult : MultiHits ) + { + if ( UWidgetComponent* HitWidgetComponent = Cast<UWidgetComponent>(HitResult.GetComponent()) ) + { + if ( HitWidgetComponent->IsVisible() ) + { + TraceResult.bWasHit = true; + TraceResult.HitResult = HitResult; + break; + } + } + else + { + break; + } + } + } + + if (TraceResult.bWasHit) + { + TraceResult.HitWidgetComponent = Cast<UWidgetComponent>(TraceResult.HitResult.GetComponent()); + if (TraceResult.HitWidgetComponent) + { + if (TraceResult.HitWidgetComponent->GetGeometryMode() == EWidgetGeometryMode::Cylinder) + { + TTuple<FVector, FVector2D> CylinderHitLocation = TraceResult.HitWidgetComponent->GetCylinderHitLocation(TraceResult.HitResult.ImpactPoint, WorldDirection); + TraceResult.HitResult.ImpactPoint = CylinderHitLocation.Get<0>(); + TraceResult.LocalHitLocation = CylinderHitLocation.Get<1>(); + } + else + { + ensure(TraceResult.HitWidgetComponent->GetGeometryMode() == EWidgetGeometryMode::Plane); + TraceResult.HitWidgetComponent->GetLocalHitLocation(TraceResult.HitResult.ImpactPoint, TraceResult.LocalHitLocation); + } + TraceResult.HitWidgetPath = FindHoveredWidgetPath(TraceResult); + } + } +// UWidgetInteractionComponent::PerformTrace() + + if ( InteractionSource == EWidgetInteractionSource::Custom ) + break; + + if ( TraceResult.bWasHit && TraceResult.HitWidgetComponent ) + { + UUserWidget* UserWidget = TraceResult.HitWidgetComponent->GetUserWidgetObject(); + if ( UserWidget && UserWidget->WidgetTree ) + { + bool bHit = false; + UserWidget->WidgetTree->ForEachWidget( [ &TraceResult, &bHit ]( UWidget* Widget ) + { + if ( Widget && !bHit ) + { + const FGeometry& Geometry = Widget->GetCachedGeometry(); + const FVector2D Size = Geometry.GetLocalSize(); + + if ( Size.X > SMALL_NUMBER && Size.Y > SMALL_NUMBER ) + { + FVector2D Location = Geometry.AbsoluteToLocal( TraceResult.LocalHitLocation ) / Size; + if ( Location.X >= 0 && Location.X < 1 && Location.Y >= 0 && Location.Y < 1 ) + { + UWebInterface* WebInterface = Cast<UWebInterface>( Widget ); + if ( WebInterface && WebInterface->IsVirtualPointerTransparencyEnabled() ) + { + const int32 X = (int32)( Location.X * WebInterface->GetTextureWidth() ); + const int32 Y = (int32)( Location.Y * WebInterface->GetTextureHeight() ); + + const FLinearColor Pixel = WebInterface->ReadTexturePixel( X, Y ); + if ( Pixel.A >= WebInterface->GetTransparencyThreshold() ) + bHit = true; + } + else + { + TSharedPtr<SWidget> SafeWidget = Widget->GetCachedWidget(); + if ( SafeWidget.IsValid() && SafeWidget->GetVisibility().IsHitTestVisible() ) + bHit = true; + } + } + } + } + } ); + + if ( !bHit ) + { + IgnoredComponents.Add( TraceResult.HitWidgetComponent ); + bLineTrace = true; + + TraceResult.bWasHit = false; + TraceResult.HitResult = FHitResult(); + + TraceResult.HitWidgetComponent = nullptr; + TraceResult.HitWidgetPath = FWidgetPath(); + } + } + } + } + + return TraceResult; +} diff --git a/Plugins/WebUI/Source/WebUI/Private/WebInterfaceObject.cpp b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceObject.cpp new file mode 100644 index 0000000000000000000000000000000000000000..09fae2dace7d971095b2352e6035403967de458d --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceObject.cpp @@ -0,0 +1,15 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "WebInterfaceObject.h" +#include "WebInterface.h" +#include "WebInterfaceCallback.h" + +void UWebInterfaceObject::Broadcast( const FString& Name, const FString& Data, const FString& Callback ) +{ + if ( !MyInterface.IsValid() ) + return; + + if ( Callback.IsEmpty() ) + MyInterface->OnInterfaceEvent.Broadcast( FName( *Name ), FJsonLibraryValue::Parse( Data ), FWebInterfaceCallback() ); + else + MyInterface->OnInterfaceEvent.Broadcast( FName( *Name ), FJsonLibraryValue::Parse( Data ), FWebInterfaceCallback( MyInterface, Callback ) ); +} diff --git a/Plugins/WebUI/Source/WebUI/Private/WebInterfaceSchemeHandler.cpp b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceSchemeHandler.cpp new file mode 100644 index 0000000000000000000000000000000000000000..1234889fd8077047a3fddee47bc2c09abee62876 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/WebInterfaceSchemeHandler.cpp @@ -0,0 +1,313 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "WebInterfaceSchemeHandler.h" +#include "WebInterfaceHelpers.h" +#if !UE_SERVER && WITH_CEF3 +#include "Async/Async.h" +#include "GenericPlatform/GenericPlatformHttp.h" +#include "HAL/FileManager.h" +#include "Http.h" + +#define BIN "application/octet-stream" +FWebInterfaceSchemeHandler::FWebInterfaceSchemeHandler() + : MimeType( BIN ) + , ContentLength( 0 ) + , TotalBytesRead( 0 ) + , Reader( nullptr ) +{ +} + +FWebInterfaceSchemeHandler::~FWebInterfaceSchemeHandler() +{ + CloseReader(); +} + +bool FWebInterfaceSchemeHandler::ProcessRequest( const FString& Verb, const FString& Url, const FSimpleDelegate& OnHeadersReady ) +{ + if ( Verb.ToUpper() != "GET" ) + return false; + + FString FilePath = Url; + if ( FilePath.Contains( "://" ) ) + { + FString Scheme; + if ( FParse::SchemeNameFromURI( *FilePath, Scheme ) ) + FilePath = FilePath.RightChop( Scheme.Len() + 3 ); + } + + int32 DesiredWidth = 0; + int32 DesiredHeight = 0; + + int32 IndexHash = FilePath.Find( "#" ); + if ( IndexHash >= 0 ) + FilePath = FilePath.Left( IndexHash ); + + int32 IndexQuery = FilePath.Find( "?" ); + if ( IndexQuery >= 0 ) + { + TArray<FString> QueryParts; + FilePath.RightChop( IndexQuery + 1 ).ParseIntoArray( QueryParts, TEXT( "&" ), true ); + + TMap<FString, FString> QueryString; + for ( int32 i = 0; i < QueryParts.Num(); i++ ) + { + FString LHS, RHS; + QueryParts[i].Split( "=", &LHS, &RHS ); + + LHS = FPlatformHttp::UrlDecode( LHS ); + RHS = FPlatformHttp::UrlDecode( RHS ); + + QueryString.Add( LHS, RHS ); + } + + if ( QueryString.Contains( "w" ) ) + DesiredWidth = FCString::Strtoi( *QueryString[ "w" ], nullptr, 10 ); + if ( QueryString.Contains( "h" ) ) + DesiredHeight = FCString::Strtoi( *QueryString[ "h" ], nullptr, 10 ); + + FilePath = FilePath.Left( IndexQuery ); + } + + const FString ContentDirectory = FPaths::ProjectContentDir(); + FilePath = ContentDirectory + FilePath; + FilePath = FilePath.Replace( TEXT( "\\" ), TEXT( "/" ) ); + FilePath = FilePath.Replace( TEXT( "//" ), TEXT( "/" ) ); + + int64 FileSize = IFileManager::Get().FileSize( *FilePath ); + if ( FileSize != INDEX_NONE ) + { + ContentLength = (int32)FileSize; + if ( FileSize > INT32_MAX ) + return false; + + MimeType = FGenericPlatformHttp::GetMimeType( FilePath ); + if ( MimeType.Len() == 0 || MimeType == "application/unknown" ) + MimeType = BIN; + + CreateReader( *FilePath ); + OnHeadersReady.Execute(); + } + else + { + FString FileName = FPaths::GetBaseFilename( FilePath ); + FString FileExtension = FPaths::GetExtension( FilePath ).ToLower(); + if ( FileExtension == "png" ) + ImageFormat = EImageFormat::PNG; + else if ( FileExtension == "jpg" + || FileExtension == "jpeg" ) + ImageFormat = EImageFormat::JPEG; + else + ImageFormat = EImageFormat::Invalid; + + MimeType = FGenericPlatformHttp::GetMimeType( FilePath ); + if ( MimeType.Len() == 0 || MimeType == "application/unknown" ) + MimeType = BIN; + + if ( ImageFormat != EImageFormat::Invalid ) + { + FilePath = "/Game" / FilePath.RightChop( ContentDirectory.Len() ); + FilePath = FilePath.LeftChop( FileExtension.Len() + 1 ); + FilePath += "." + FileName; + + AsyncTask( ENamedThreads::GameThread, [this, FilePath, DesiredWidth, DesiredHeight, OnHeadersReady]() + { + if ( !CreateImageReader( FilePath, DesiredWidth, DesiredHeight ) ) + ImageFormat = EImageFormat::Invalid; + + OnHeadersReady.Execute(); + } ); + } + else + OnHeadersReady.Execute(); + } + + return true; +} + +void FWebInterfaceSchemeHandler::GetResponseHeaders( IHeaders& OutHeaders ) +{ + if ( ImageSize.X > 0 && ImageSize.Y > 0 && ImagePixels.Num() > 0 && ImageFormat != EImageFormat::Invalid ) + { + ContentLength = (int32)GetCompressedImageData(); + if ( ContentLength <= 0 ) + MimeType = BIN; + } + + ImagePixels.Empty(); + if ( Reader || ImageData.Num() > 0 ) + { + OutHeaders.SetStatusCode( 200 ); + OutHeaders.SetMimeType( *MimeType ); + OutHeaders.SetContentLength( ContentLength ); + } + else + OutHeaders.SetStatusCode( 404 ); +} + +bool FWebInterfaceSchemeHandler::ReadResponse( uint8* OutBytes, int32 BytesToRead, int32& BytesRead, const FSimpleDelegate& OnMoreDataReady ) +{ + BytesRead = 0; + if ( !Reader && ImageData.Num() <= 0 ) + return false; + + BytesRead = ContentLength - TotalBytesRead; + if ( BytesRead <= 0 ) + return false; + + if ( BytesRead > BytesToRead ) + BytesRead = BytesToRead; + + if ( ImageData.Num() > 0 ) + FMemory::Memcpy( OutBytes, ImageData.GetData() + TotalBytesRead, BytesRead); + else + Reader->Serialize( OutBytes, BytesRead ); + + TotalBytesRead += BytesRead; + if ( TotalBytesRead < ContentLength ) + OnMoreDataReady.Execute(); + else + CloseReader(); + + return true; +} + +void FWebInterfaceSchemeHandler::Cancel() +{ + CloseReader(); +} + +bool FWebInterfaceSchemeHandler::CreateReader( const FString& FilePath ) +{ + if ( Reader ) + CloseReader(); + + Reader = IFileManager::Get().CreateFileReader( *FilePath ); + if ( !Reader ) + return false; + + return true; +} + +void FWebInterfaceSchemeHandler::CloseReader() +{ + MimeType = BIN; + + ContentLength = 0; + TotalBytesRead = 0; + + ImageFormat = EImageFormat::Invalid; + ImageSize = FIntPoint::ZeroValue; + + ImagePixels.Empty(); + ImageData.Empty(); + + if ( !Reader ) + return; + + Reader->Close(); + + delete Reader; + Reader = nullptr; +} + +bool FWebInterfaceSchemeHandler::CreateImageReader( const FString& FilePath, int32 DesiredWidth, int32 DesiredHeight ) +{ + TSoftObjectPtr AssetPtr; + AssetPtr = FilePath; + + check( IsInGameThread() ); + + UObject* Asset = AssetPtr.LoadSynchronous(); + if ( Asset ) + { + UTexture2D* Texture = Cast<UTexture2D>( Asset ); + if ( Texture ) + { + ImageSize = UWebInterfaceHelpers::GenerateImageFromTexture( ImagePixels, Texture ); + ResizeImageReader( DesiredWidth, DesiredHeight ); + + return true; + } + + UMaterialInterface* Material = Cast<UMaterialInterface>( Asset ); + if ( Material ) + { + ImageSize = UWebInterfaceHelpers::GenerateImageFromMaterial( ImagePixels, Material, DesiredWidth, DesiredHeight ); + return true; + } + + UTextureRenderTarget2D* RenderTarget = Cast<UTextureRenderTarget2D>( Asset ); + if ( RenderTarget ) + { + ImageSize = UWebInterfaceHelpers::GenerateImageFromRenderTarget( ImagePixels, RenderTarget ); + ResizeImageReader( DesiredWidth, DesiredHeight ); + + return true; + } + } + + return false; +} + +bool FWebInterfaceSchemeHandler::ResizeImageReader( int32 DesiredWidth, int32 DesiredHeight ) +{ + int32 Width = DesiredWidth; + int32 Height = DesiredHeight; + + if ( Width > 0 && Height > 0 ) + { + if ( Width == ImageSize.X && Height == ImageSize.Y ) + return false; + } + else if ( Width > 0 ) + { + if ( Width == ImageSize.X ) + return false; + + Height = FMath::RoundToInt32( Width * ( (float)ImageSize.Y / (float)ImageSize.X ) ); + if ( Height <= 0 ) + return false; + } + else if ( Height > 0 ) + { + if ( Height == ImageSize.Y ) + return false; + + Width = FMath::RoundToInt32( Height * ( (float)ImageSize.X / (float)ImageSize.Y ) ); + if ( Width <= 0 ) + return false; + } + else + return false; + + ImagePixels = UWebInterfaceHelpers::ResizeImage( ImageSize.X, ImageSize.Y, ImagePixels, Width, Height ); + ImageSize = FIntPoint( Width, Height ); + + return true; +} + +int64 FWebInterfaceSchemeHandler::GetCompressedImageData() +{ + if ( ImageFormat == EImageFormat::Invalid ) + return 0; + if ( ImageSize.X <= 0 || ImageSize.Y <= 0 ) + return 0; + if ( ImagePixels.Num() <= 0 ) + return 0; + + IImageWrapper* ImageWrapper = UWebInterfaceHelpers::FindOrCreateImageWrapper( ImageFormat ); + if ( !ImageWrapper ) + return 0; + + if ( UWebInterfaceHelpers::SetImageWrapper( ImageWrapper, ImagePixels, ImageSize ) ) + ImageData = ImageWrapper->GetCompressed(); + + UWebInterfaceHelpers::ReturnImageWrapper( ImageWrapper ); + return ImageData.Num(); +} + + +TUniquePtr<IWebInterfaceBrowserSchemeHandler> FWebInterfaceSchemeHandlerFactory::Create( FString Verb, FString Url ) +{ + return MakeUnique<FWebInterfaceSchemeHandler>(); +} +#endif diff --git a/Plugins/WebUI/Source/WebUI/Private/WebUIModule.cpp b/Plugins/WebUI/Source/WebUI/Private/WebUIModule.cpp new file mode 100644 index 0000000000000000000000000000000000000000..f38c11e13547c7dc190c5f8b1e997264d10f0ac1 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Private/WebUIModule.cpp @@ -0,0 +1,65 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#include "WebUIModule.h" +#include "Modules/ModuleManager.h" + +#if !UE_SERVER +#include "WebInterfaceAssetManager.h" +#include "Materials/Material.h" +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX +#include "IWebInterfaceBrowserSingleton.h" +#include "WebInterfaceBrowserModule.h" +#else +#include "IWebBrowserSingleton.h" +#include "WebBrowserModule.h" +#endif +#endif + +class FWebUIModule : public IWebUIModule +{ +public: + virtual void StartupModule() override + { +#if !UE_SERVER + if (WebInterfaceAssetMgr == nullptr) + { +#if PLATFORM_WINDOWS || PLATFORM_MAC || PLATFORM_LINUX + //IWebInterfaceBrowserModule::Get(); // force the module to load + //if (IWebInterfaceBrowserModule::IsAvailable() && IWebInterfaceBrowserModule::Get().IsWebModuleAvailable()) + //{ + // IWebInterfaceBrowserSingleton* WebBrowserSingleton = IWebInterfaceBrowserModule::Get().GetSingleton(); + // if (WebBrowserSingleton) + // { + // WebBrowserSingleton->SetDefaultMaterial(WebInterfaceAssetMgr->GetDefaultMaterial()); + // WebBrowserSingleton->SetDefaultTranslucentMaterial(WebInterfaceAssetMgr->GetDefaultTranslucentMaterial()); + // } + //} +#else + WebInterfaceAssetMgr = NewObject<UWebInterfaceAssetManager>((UObject*)GetTransientPackage(), NAME_None, RF_Transient | RF_Public); + WebInterfaceAssetMgr->LoadDefaultMaterials(); + + IWebBrowserModule::Get(); // force the module to load + if (IWebBrowserModule::IsAvailable() && IWebBrowserModule::Get().IsWebModuleAvailable()) + { + IWebBrowserSingleton* WebBrowserSingleton = IWebBrowserModule::Get().GetSingleton(); + if (WebBrowserSingleton) + { + WebBrowserSingleton->SetDefaultMaterial(WebInterfaceAssetMgr->GetDefaultMaterial()); + WebBrowserSingleton->SetDefaultTranslucentMaterial(WebInterfaceAssetMgr->GetDefaultTranslucentMaterial()); + } + } +#endif + } +#endif + } + + virtual void ShutdownModule() override + { + } + +#if !UE_SERVER +private: + UWebInterfaceAssetManager* WebInterfaceAssetMgr; +#endif +}; + +IMPLEMENT_MODULE(FWebUIModule, WebUI); diff --git a/Plugins/WebUI/Source/WebUI/Public/SWebInterface.h b/Plugins/WebUI/Source/WebUI/Public/SWebInterface.h new file mode 100644 index 0000000000000000000000000000000000000000..4a87bc528c1fa687d369ed6cfdc253a824028e05 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/SWebInterface.h @@ -0,0 +1,189 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Engine/Texture.h" +#include "Layout/Visibility.h" +#include "Widgets/DeclarativeSyntaxSupport.h" +#include "Widgets/SCompoundWidget.h" +#include "Widgets/SWindow.h" + +#if !UE_SERVER +#if PLATFORM_ANDROID || PLATFORM_IOS +#include "SWebBrowser.h" +#include "SWebBrowserView.h" +#include "IWebBrowserPopupFeatures.h" +#include "IWebBrowserWindow.h" +#else +#include "SWebInterfaceBrowserView.h" +#endif + +#if PLATFORM_ANDROID || PLATFORM_IOS +typedef SWebBrowser SWebInterfaceBrowser; +typedef SWebBrowserView SWebInterfaceBrowserView; +typedef IWebBrowserAdapter IWebInterfaceBrowserAdapter; +typedef IWebBrowserDialog IWebInterfaceBrowserDialog; +typedef IWebBrowserWindow IWebInterfaceBrowserWindow; +typedef IWebBrowserPopupFeatures IWebInterfaceBrowserPopupFeatures; +typedef EWebBrowserDialogEventResponse EWebInterfaceBrowserDialogEventResponse; +#else +class SWebInterfaceBrowserView; +class IWebInterfaceBrowserAdapter; +class IWebInterfaceBrowserDialog; +class IWebInterfaceBrowserWindow; +class IWebInterfaceBrowserPopupFeatures; +enum class EWebInterfaceBrowserDialogEventResponse; +#endif +struct FWebNavigationRequest; + +class WEBUI_API SWebInterface : public SCompoundWidget +{ +public: + DECLARE_DELEGATE_RetVal_TwoParams( bool, FOnBeforeBrowse, const FString& /*Url*/, const FWebNavigationRequest& /*Request*/ ); + DECLARE_DELEGATE_RetVal_ThreeParams( bool, FOnLoadUrl, const FString& /*Method*/, const FString& /*Url*/, FString& /* Response */ ); + DECLARE_DELEGATE_RetVal_OneParam( EWebInterfaceBrowserDialogEventResponse, FOnShowDialog, const TWeakPtr<IWebInterfaceBrowserDialog>& ); + + DECLARE_DELEGATE_RetVal( bool, FOnSuppressContextMenu ); + + SLATE_BEGIN_ARGS( SWebInterface ) + : _FrameRate( 60 ) + , _InitialURL( TEXT( "http://tracerinteractive.com" ) ) + , _BackgroundColor( 255, 255, 255, 255 ) + , _EnableMouseTransparency( false ) + , _EnableVirtualPointerTransparency( false ) + , _TransparencyDelay( 0.1f ) + , _TransparencyThreshold( 0.333f ) + , _TransparencyTick( 0.0f ) + , _ViewportSize( FVector2D::ZeroVector ) + { + _Visibility = EVisibility::SelfHitTestInvisible; + } + SLATE_ARGUMENT( TSharedPtr<SWindow>, ParentWindow ) + SLATE_ARGUMENT( int32, FrameRate ) + SLATE_ARGUMENT( FString, InitialURL ) + SLATE_ARGUMENT( TOptional<FString>, ContentsToLoad ) + SLATE_ARGUMENT( FColor, BackgroundColor ) + SLATE_ARGUMENT( bool, AcceleratedPaint ) + SLATE_ARGUMENT( bool, NativeCursors ) + SLATE_ARGUMENT( bool, EnableMouseTransparency ) + SLATE_ARGUMENT( bool, EnableVirtualPointerTransparency ) + SLATE_ARGUMENT( float, TransparencyDelay ) + SLATE_ARGUMENT( float, TransparencyThreshold ) + SLATE_ARGUMENT( float, TransparencyTick ) + SLATE_ARGUMENT( TOptional<EPopupMethod>, PopupMenuMethod ) + + SLATE_ATTRIBUTE( FVector2D, ViewportSize ); + + SLATE_EVENT( FSimpleDelegate, OnLoadCompleted ) + SLATE_EVENT( FSimpleDelegate, OnLoadError ) + SLATE_EVENT( FSimpleDelegate, OnLoadStarted ) + SLATE_EVENT( FOnTextChanged, OnTitleChanged ) + SLATE_EVENT( FOnTextChanged, OnUrlChanged ) + SLATE_EVENT( FOnBeforePopupDelegate, OnBeforePopup ) + SLATE_EVENT( FOnCreateWindowDelegate, OnCreateWindow ) + SLATE_EVENT( FOnCloseWindowDelegate, OnCloseWindow ) + SLATE_EVENT( FOnBeforeBrowse, OnBeforeNavigation ) + SLATE_EVENT( FOnLoadUrl, OnLoadUrl ) + SLATE_EVENT( FOnShowDialog, OnShowDialog ) + SLATE_EVENT( FSimpleDelegate, OnDismissAllDialogs ) + SLATE_EVENT( FOnSuppressContextMenu, OnSuppressContextMenu ); + SLATE_END_ARGS() + + SWebInterface(); + ~SWebInterface(); + + void Construct( const FArguments& InArgs ); + + virtual void Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime ) override; + virtual bool SupportsKeyboardFocus() const override { return true; } + +private: + + FLinearColor LastMousePixel; + float LastMouseTime; + float LastMouseTick; + + EVisibility GetViewportVisibility() const; + + bool HandleBeforePopup( FString URL, FString Frame ); + bool HandleSuppressContextMenu(); + + bool HandleCreateWindow( const TWeakPtr<IWebInterfaceBrowserWindow>& NewBrowserWindow, const TWeakPtr<IWebInterfaceBrowserPopupFeatures>& PopupFeatures ); + bool HandleCloseWindow( const TWeakPtr<IWebInterfaceBrowserWindow>& BrowserWindowPtr ); + +protected: + + static bool bPAK; + + TSharedPtr<SWebInterfaceBrowserView> BrowserView; + TSharedPtr<IWebInterfaceBrowserWindow> BrowserWindow; + +#if UE_BUILD_DEVELOPMENT || UE_BUILD_DEBUG + TMap<TWeakPtr<IWebInterfaceBrowserWindow>, TWeakPtr<SWindow>> BrowserWindowWidgets; +#endif + + bool bAcceleratedPaint; + bool bMouseTransparency; + bool bVirtualPointerTransparency; + + float TransparencyDelay; + float TransparencyThreshold; + float TransparencyTick; + + + FSimpleDelegate OnLoadCompleted; + FSimpleDelegate OnLoadError; + FSimpleDelegate OnLoadStarted; + + FOnTextChanged OnTitleChanged; + FOnTextChanged OnUrlChanged; + + FOnBeforePopupDelegate OnBeforePopup; + FOnCreateWindowDelegate OnCreateWindow; + FOnCloseWindowDelegate OnCloseWindow; + + FOnBeforeBrowse OnBeforeNavigation; + FOnLoadUrl OnLoadUrl; + + FOnShowDialog OnShowDialog; + FSimpleDelegate OnDismissAllDialogs; + +public: + + bool HasMouseTransparency() const; + bool HasVirtualPointerTransparency() const; + + float GetTransparencyDelay() const; + float GetTransparencyThreshold() const; + float GetTransparencyTick() const; + + int32 GetTextureWidth() const; + int32 GetTextureHeight() const; + + FColor ReadTexturePixel( int32 X, int32 Y ) const; + TArray<FColor> ReadTexturePixels( int32 X, int32 Y, int32 Width, int32 Height ) const; + + void LoadURL( FString NewURL ); + void LoadString( FString Contents, FString DummyURL ); + + void Reload(); + void StopLoad(); + + FString GetUrl() const; + + bool IsLoaded() const; + bool IsLoading() const; + + void ExecuteJavascript( const FString& ScriptText ); + + void BindUObject( const FString& Name, UObject* Object, bool bIsPermanent = true ); + void UnbindUObject( const FString& Name, UObject* Object, bool bIsPermanent = true ); + + void BindAdapter( const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter ); + void UnbindAdapter( const TSharedRef<IWebInterfaceBrowserAdapter>& Adapter ); + + void BindInputMethodSystem( ITextInputMethodSystem* TextInputMethodSystem ); + void UnbindInputMethodSystem(); + + void SetParentWindow( TSharedPtr<SWindow> Window ); +}; +#endif diff --git a/Plugins/WebUI/Source/WebUI/Public/WebInterface.h b/Plugins/WebUI/Source/WebUI/Public/WebInterface.h new file mode 100644 index 0000000000000000000000000000000000000000..3893d6bc176fc5cfe92365b6cdeccf4f30c8d5aa --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/WebInterface.h @@ -0,0 +1,176 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "Components/Widget.h" +#include "Engine/EngineBaseTypes.h" +#include "JsonLibrary.h" +#include "WebInterfaceCallback.h" +#include "WebInterface.generated.h" + +class UMaterial; + +UENUM(BlueprintType, meta = (DisplayName = "UI Directory")) +enum class EWebInterfaceDirectory : uint8 +{ + UI UMETA(DisplayName="/UI"), + Content UMETA(DisplayName="/Content") +}; + +UCLASS() +class WEBUI_API UWebInterface : public UWidget +{ + GENERATED_UCLASS_BODY() + +public: + + DECLARE_DYNAMIC_MULTICAST_DELEGATE_OneParam( FOnUrlChangedEvent, const FText&, URL ); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_TwoParams( FOnPopupEvent, const FString&, URL, const FString&, Frame ); + DECLARE_DYNAMIC_MULTICAST_DELEGATE_ThreeParams( FOnInterfaceEvent, const FName, Name, FJsonLibraryValue, Data, FWebInterfaceCallback, Callback ); + + // Load the browser. + UFUNCTION(BlueprintCallable, Category = "Web UI") + bool Load( const FString& File ); + // Load HTML in the browser. + UFUNCTION(BlueprintCallable, Category = "Web UI") + void LoadHTML( const FString& HTML ); + // Load a URL in the browser. + UFUNCTION(BlueprintCallable, Category = "Web UI") + void LoadURL( const FString& URL ); + // Load a file in the browser. + UFUNCTION(BlueprintCallable, Category = "Web UI", meta = (AdvancedDisplay = "Directory")) + void LoadFile( const FString& File, EWebInterfaceDirectory Directory = EWebInterfaceDirectory::UI ); + // Load content into the browser. + UFUNCTION(BlueprintCallable, Category = "Web UI", meta = (AdvancedDisplay = "bScript")) + bool LoadContent( const FString& File, bool bScript = false ); + + // Get the current URL of the browser. + UFUNCTION(BlueprintCallable, Category = "Web UI") + FString GetURL() const; + + // Execute JavaScript in the browser context. + UFUNCTION(BlueprintCallable, Category = "Web UI") + void Execute( const FString& Script ); + // Call ue.interface.function(data) in the browser context. + UFUNCTION(BlueprintCallable, Category = "Web UI", meta = (AdvancedDisplay = "Data", AutoCreateRefTerm = "Data")) + void Call( const FString& Function, const FJsonLibraryValue& Data ); + + // Bind an object to ue.name in the browser context. + UFUNCTION(BlueprintCallable, Category = "Web UI") + void Bind( const FString& Name, UObject* Object ); + // Unbind an object from ue.name in the browser context. + UFUNCTION(BlueprintCallable, Category = "Web UI") + void Unbind( const FString& Name, UObject* Object ); + + // Enables input method editors for different languages. + UFUNCTION(BlueprintCallable, Category = "Web UI|Input") + void EnableIME(); + // Disables input method editors for different languages. + UFUNCTION(BlueprintCallable, Category = "Web UI|Input") + void DisableIME(); + + // Set focus to the browser. + UFUNCTION(BlueprintCallable, meta = (AdvancedDisplay = "MouseLockMode"), Category = "Web UI|Helpers") + void Focus( EMouseLockMode MouseLockMode = EMouseLockMode::LockOnCapture ); + // Set focus to the game. + UFUNCTION(BlueprintCallable, meta = (AdvancedDisplay = "MouseCaptureMode"), Category = "Web UI|Helpers") + void Unfocus( EMouseCaptureMode MouseCaptureMode = EMouseCaptureMode::CapturePermanently ); + // Reset cursor to center of the viewport. + UFUNCTION(BlueprintCallable, Category = "Web UI|Helpers") + void ResetMousePosition(); + + // Check if accelerated paint is being used. + UFUNCTION(BlueprintPure, Category = "Web UI|Helpers") + bool IsUsingAcceleratedPaint() const; + + // Check if mouse transparency is enabled. + UFUNCTION(BlueprintPure, Category = "Web UI|Transparency") + bool IsMouseTransparencyEnabled() const; + // Check if virtual pointer transparency is enabled. + UFUNCTION(BlueprintPure, Category = "Web UI|Transparency") + bool IsVirtualPointerTransparencyEnabled() const; + + // Get the transparency delay of the browser texture. + UFUNCTION(BlueprintPure, Category = "Web UI|Transparency") + float GetTransparencyDelay() const; + // Get the transparency threshold of the browser texture. + UFUNCTION(BlueprintPure, Category = "Web UI|Transparency") + float GetTransparencyThreshold() const; + // Get the transparency tick rate of the browser texture. + UFUNCTION(BlueprintPure, Category = "Web UI|Transparency") + float GetTransparencyTick() const; + + // Get the width of the browser texture. + UFUNCTION(BlueprintPure, Category = "Web UI|Textures") + int32 GetTextureWidth() const; + // Get the height of the browser texture. + UFUNCTION(BlueprintPure, Category = "Web UI|Textures") + int32 GetTextureHeight() const; + + // Read a pixel from the browser texture. + UFUNCTION(BlueprintCallable, Category = "Web UI|Textures") + FColor ReadTexturePixel( int32 X, int32 Y ); + // Read an area of pixels from the browser texture. + UFUNCTION(BlueprintCallable, Category = "Web UI|Textures") + TArray<FColor> ReadTexturePixels( int32 X, int32 Y, int32 Width, int32 Height ); + + // Called when the URL has changed. + UPROPERTY(BlueprintAssignable, Category = "Web UI|Events") + FOnUrlChangedEvent OnUrlChangedEvent; + // Called when a popup is requested. + UPROPERTY(BlueprintAssignable, Category = "Web UI|Events") + FOnPopupEvent OnPopupEvent; + + // Called with ue.interface.broadcast(name, data) in the browser context. + UPROPERTY(BlueprintAssignable, Category = "Web UI|Events") + FOnInterfaceEvent OnInterfaceEvent; + + virtual void ReleaseSlateResources( bool bReleaseChildren ) override; + +#if WITH_EDITOR + virtual const FText GetPaletteCategory() override; +#endif + + UMaterial* GetDefaultMaterial() const; + +private: + + UPROPERTY() + class UWebInterfaceObject* MyObject; + + void HandleUrlChanged( const FText& URL ); + bool HandleBeforePopup( FString URL, FString Frame ); + +protected: + + UPROPERTY(EditAnywhere, Category = "Behavior", meta = (UIMin = 1, UIMax = 60)) + int32 FrameRate; + UPROPERTY(EditAnywhere, Category = "Behavior") + FString InitialURL; + UPROPERTY(EditAnywhere, Category = "Behavior", AdvancedDisplay) + bool bAcceleratedPaint; + + UPROPERTY(EditAnywhere, meta = (DisplayName = "Enable Transparency"), Category = "Behavior|Mouse") + bool bEnableMouseTransparency; + UPROPERTY(EditAnywhere, meta = (DisplayName = "Transparency Threshold", UIMin = 0, UIMax = 1), Category = "Behavior|Mouse") + float MouseTransparencyThreshold; + UPROPERTY(EditAnywhere, meta = (DisplayName = "Transparency Delay", UIMin = 0, UIMax = 1), Category = "Behavior|Mouse") + float MouseTransparencyDelay; + UPROPERTY(EditAnywhere, meta = (DisplayName = "Transparency Tick", UIMin = 0, UIMax = 1), Category = "Behavior|Mouse") + float MouseTransparencyTick; + + UPROPERTY(EditAnywhere, meta = (DisplayName = "Enable Transparency"), Category = "Behavior|Virtual Pointer") + bool bEnableVirtualPointerTransparency; + UPROPERTY(EditAnywhere, meta = (DisplayName = "Transparency Threshold", UIMin = 0, UIMax = 1), Category = "Behavior|Virtual Pointer") + float VirtualPointerTransparencyThreshold; + + UPROPERTY(EditAnywhere, meta = (DisplayName = "Custom Cursors"), Category = "Behavior|Mouse") + bool bCustomCursors; + +#if !UE_SERVER + TSharedPtr<class SWebInterface> WebInterfaceWidget; +#endif + virtual TSharedRef<SWidget> RebuildWidget() override; + +private: + + UMaterial* DefaultMaterial; +}; diff --git a/Plugins/WebUI/Source/WebUI/Public/WebInterfaceAssetManager.h b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceAssetManager.h new file mode 100644 index 0000000000000000000000000000000000000000..82abc9a9d458a6b59a4658273b73e27a0d257d7d --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceAssetManager.h @@ -0,0 +1,27 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once + +#include "UObject/SoftObjectPtr.h" +#include "Materials/Material.h" +#include "WebInterfaceAssetManager.generated.h" + +class UMaterial; + +UCLASS() +class WEBUI_API UWebInterfaceAssetManager : public UObject +{ + GENERATED_UCLASS_BODY() + +public: + + void LoadDefaultMaterials(); + + UMaterial* GetDefaultMaterial(); + UMaterial* GetDefaultTranslucentMaterial(); + +protected: + + UPROPERTY() + TSoftObjectPtr<UMaterial> DefaultMaterial; + TSoftObjectPtr<UMaterial> DefaultTranslucentMaterial; +}; diff --git a/Plugins/WebUI/Source/WebUI/Public/WebInterfaceCallback.h b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceCallback.h new file mode 100644 index 0000000000000000000000000000000000000000..fcbabfbbb0ed9082e4569e05ea22cf911037a02d --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceCallback.h @@ -0,0 +1,31 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "JsonLibrary.h" +#include "WebInterfaceCallback.generated.h" + +class UWebInterface; + +USTRUCT(BlueprintType) +struct WEBUI_API FWebInterfaceCallback +{ + friend class UWebInterface; + friend class UWebInterfaceObject; + + GENERATED_USTRUCT_BODY() + + FWebInterfaceCallback(); + +protected: + + FWebInterfaceCallback( TWeakObjectPtr<UWebInterface> Interface, const FString& Callback ); + +public: + + bool IsValid() const; + void Call( const FJsonLibraryValue& Data ); + +private: + + FString MyCallback; + TWeakObjectPtr<UWebInterface> MyInterface; +}; diff --git a/Plugins/WebUI/Source/WebUI/Public/WebInterfaceHelpers.h b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceHelpers.h new file mode 100644 index 0000000000000000000000000000000000000000..f1c0b53a92b6d59fc64075b2af0f44859cf407fa --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceHelpers.h @@ -0,0 +1,44 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "CoreMinimal.h" +#include "Kismet/BlueprintFunctionLibrary.h" +#include "IImageWrapper.h" +#include "JsonLibrary.h" +#include "WebInterfaceCallback.h" +#include "WebInterfaceHelpers.generated.h" + +UCLASS() +class WEBUI_API UWebInterfaceHelpers : public UBlueprintFunctionLibrary +{ + GENERATED_BODY() + +#if UE_EDITOR +protected: +#else +public: +#endif + + // Check if this callback is valid. + UFUNCTION(BlueprintPure, meta = (DisplayName = "Valid"), Category = "Web UI|Callback") + static bool WebInterfaceCallback_IsValid( UPARAM(ref) const FWebInterfaceCallback& Target ); + // Call ue.interface.callback(data) in the browser context. + UFUNCTION(BlueprintCallable, meta = (DisplayName = "Call", AdvancedDisplay = "Data", AutoCreateRefTerm = "Data"), Category = "Web UI|Callback") + static void WebInterfaceCallback_Call( UPARAM(ref) FWebInterfaceCallback& Target, const FJsonLibraryValue& Data ); + +public: + + static TArray64<FColor> ResizeImage( int32 SrcWidth, int32 SrcHeight, const TArray64<FColor>& SrcData, int32 DstWidth, int32 DstHeight, bool bLinearSpace = false, bool bForceOpaqueOutput = false ); + + static IImageWrapper* FindOrCreateImageWrapper( EImageFormat Format ); + static bool SetImageWrapper( IImageWrapper* Wrapper, const TArray64<FColor>& Pixels, FIntPoint Size ); + static void ReturnImageWrapper( IImageWrapper* Wrapper ); + + static FIntPoint GenerateImageFromRenderTarget( TArray64<FColor>& OutPixels, class UTextureRenderTarget2D* RenderTarget ); + static FIntPoint GenerateImageFromTexture( TArray64<FColor>& OutPixels, class UTexture2D* Texture ); + static FIntPoint GenerateImageFromMaterial( TArray64<FColor>& OutPixels, class UMaterialInterface* Material, int32 Width, int32 Height ); + +private: + + static bool GenerateImageFromCamera( TArray64<FColor>& OutPixels, UObject* WorldContextObject, const FTransform& CameraTransform, int32 Width, int32 Height, float FOVDegrees = 50.0f, float MinZ = 50.0f, float Gamma = 2.2f ); + static bool GenerateImageFromScene( TArray64<FColor>& OutPixels, FSceneInterface* Scene, const FVector& ViewOrigin, const FMatrix& ViewRotationMatrix, const FMatrix& ProjectionMatrix, FIntPoint TargetSize, float TargetGamma ); +}; diff --git a/Plugins/WebUI/Source/WebUI/Public/WebInterfaceInteractionComponent.h b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceInteractionComponent.h new file mode 100644 index 0000000000000000000000000000000000000000..a26caf8f4b2324e35ba29caf8bac1d25b2230fe7 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceInteractionComponent.h @@ -0,0 +1,14 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "Components/WidgetInteractionComponent.h" +#include "WebInterfaceInteractionComponent.generated.h" + +UCLASS(ClassGroup = "UserInterface", meta = (BlueprintSpawnableComponent)) +class WEBUI_API UWebInterfaceInteractionComponent : public UWidgetInteractionComponent +{ + GENERATED_UCLASS_BODY() + +protected: + + virtual FWidgetTraceResult PerformTrace() const override; +}; diff --git a/Plugins/WebUI/Source/WebUI/Public/WebInterfaceObject.h b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceObject.h new file mode 100644 index 0000000000000000000000000000000000000000..b48234addfd4af467bba000db398109acfe5d4e6 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceObject.h @@ -0,0 +1,23 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "JsonLibrary.h" +#include "WebInterfaceObject.generated.h" + +class UWebInterface; + +UCLASS() +class WEBUI_API UWebInterfaceObject : public UObject +{ + friend class UWebInterface; + + GENERATED_BODY() + +public: + + UFUNCTION(BlueprintCallable, Category = "Web UI") + void Broadcast( const FString& Name, const FString& Data, const FString& Callback ); + +private: + + TWeakObjectPtr<UWebInterface> MyInterface; +}; diff --git a/Plugins/WebUI/Source/WebUI/Public/WebInterfaceSchemeHandler.h b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceSchemeHandler.h new file mode 100644 index 0000000000000000000000000000000000000000..b4dd23d77c5cee7082aeaba00f00f6fc2fb3ae35 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/WebInterfaceSchemeHandler.h @@ -0,0 +1,43 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#if !UE_SERVER && WITH_CEF3 +#include "IWebInterfaceBrowserSchemeHandler.h" +#include "IImageWrapper.h" + +class WEBUI_API FWebInterfaceSchemeHandler : public IWebInterfaceBrowserSchemeHandler +{ +public: + FWebInterfaceSchemeHandler(); + virtual ~FWebInterfaceSchemeHandler(); + + virtual bool ProcessRequest( const FString& Verb, const FString& Url, const FSimpleDelegate& OnHeadersReady ) override; + virtual void GetResponseHeaders( IHeaders& OutHeaders ) override; + virtual bool ReadResponse( uint8* OutBytes, int32 BytesToRead, int32& BytesRead, const FSimpleDelegate& OnMoreDataReady ) override; + virtual void Cancel() override; + +protected: + FString MimeType; + + int32 ContentLength; + int32 TotalBytesRead; + + FArchive* Reader; + + bool CreateReader( const FString& FilePath ); + void CloseReader(); + + EImageFormat ImageFormat; + FIntPoint ImageSize; + TArray64<FColor> ImagePixels; + TArray64<uint8> ImageData; + + bool CreateImageReader( const FString& FilePath, int32 DesiredWidth, int32 DesiredHeight ); + bool ResizeImageReader( int32 DesiredWidth, int32 DesiredHeight ); + int64 GetCompressedImageData(); +}; + +class WEBUI_API FWebInterfaceSchemeHandlerFactory : public IWebInterfaceBrowserSchemeHandlerFactory +{ + virtual TUniquePtr<IWebInterfaceBrowserSchemeHandler> Create( FString Verb, FString Url ) override; +}; +#endif diff --git a/Plugins/WebUI/Source/WebUI/Public/WebUIModule.h b/Plugins/WebUI/Source/WebUI/Public/WebUIModule.h new file mode 100644 index 0000000000000000000000000000000000000000..5e6b13d556b5581542a298de47f641def785a947 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/Public/WebUIModule.h @@ -0,0 +1,18 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +#pragma once +#include "Modules/ModuleManager.h" + +class IWebUIModule : public IModuleInterface +{ +public: + + static inline IWebUIModule& Get() + { + return FModuleManager::LoadModuleChecked<IWebUIModule>( "WebUI" ); + } + + static inline bool IsAvailable() + { + return FModuleManager::Get().IsModuleLoaded( "WebUI" ); + } +}; diff --git a/Plugins/WebUI/Source/WebUI/WebUI.Build.cs b/Plugins/WebUI/Source/WebUI/WebUI.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..ca92fcd53216d7d544628e2301630ef7e3d25b31 --- /dev/null +++ b/Plugins/WebUI/Source/WebUI/WebUI.Build.cs @@ -0,0 +1,92 @@ +// Copyright 2022 Tracer Interactive, LLC. All Rights Reserved. +namespace UnrealBuildTool.Rules +{ + public class WebUI : ModuleRules + { + public WebUI(ReadOnlyTargetRules Target) : base(Target) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "Core", + "CoreUObject", + "Engine", + "InputCore", + "RenderCore", + "RHI", + "Slate", + "SlateCore", + "UMG", + "ImageWriteQueue", + "JsonLibrary" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "HTTP" + } + ); + + if (Target.bBuildEditor == true) + { + PrivateIncludePathModuleNames.AddRange( + new string[] + { + "UnrealEd", + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "EditorFramework", + "UnrealEd", + } + ); + } + + if (Target.Type != TargetType.Server) + { + if (Target.Platform == UnrealTargetPlatform.Win64 + || Target.Platform == UnrealTargetPlatform.Mac + || Target.Platform == UnrealTargetPlatform.Linux) + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "WebBrowserUI" + } + ); + } + else + { + PublicDependencyModuleNames.AddRange( + new string[] + { + "WebBrowser" + } + ); + } + + if (Target.bBuildEditor || Target.Platform == UnrealTargetPlatform.Android || Target.Platform == UnrealTargetPlatform.IOS) + { + PrivateIncludePathModuleNames.AddRange( + new string[] + { + "WebBrowserTexture" + } + ); + + PrivateDependencyModuleNames.AddRange( + new string[] + { + "WebBrowserTexture" + } + ); + } + } + } + } +} diff --git a/Plugins/WebUI/WebUI.uplugin b/Plugins/WebUI/WebUI.uplugin new file mode 100644 index 0000000000000000000000000000000000000000..bf2a87a292eb1b0e706adead734ecd6e0b10a5c1 --- /dev/null +++ b/Plugins/WebUI/WebUI.uplugin @@ -0,0 +1,50 @@ +{ + "FileVersion": 3, + "Version": 1, + "VersionName": "1.0", + "FriendlyName": "Web UI", + "Description": "Create a web-based user interface using blueprints.", + "Category": "Widgets", + "CreatedBy": "Tracer Interactive", + "CreatedByURL": "https://tracerinteractive.com", + "DocsURL": "https://cdn.tracerinteractive.com/webui/documentation.pdf", + "MarketplaceURL": "", + "SupportURL": "", + "EngineVersion": "5.1.0", + "CanContainContent": false, + "Installed": true, + "Modules": [ + { + "Name": "WebBrowserUI", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ + "Win64", + "Mac", + "Linux" + ] + }, + { + "Name": "WebUI", + "Type": "Runtime", + "LoadingPhase": "PreDefault", + "WhitelistPlatforms": [ + "Win64", + "Mac", + "Linux", + "Android", + "IOS" + ] + } + ], + "Plugins": [ + { + "Name": "WebBrowserWidget", + "Enabled": true + }, + { + "Name": "JsonLibrary", + "Enabled": true + } + ] +} \ No newline at end of file diff --git a/README.md b/README.md index 36dce1855ead620d35c3fb14fa6f90e3416aefb4..42ee8194d0e9116cd2013305aab8dd5d48f10f01 100644 --- a/README.md +++ b/README.md @@ -1,92 +1,223 @@ -# CyberArchPlugins - - - -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - -``` -cd existing_repo -git remote add origin https://gitlab.nrp-nautilus.io/cyberarch/cyberarchplugins.git -git branch -M main -git push -uf origin main -``` - -## Integrate with your tools - -- [ ] [Set up project integrations](https://gitlab.nrp-nautilus.io/cyberarch/cyberarchplugins/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Automatically merge when pipeline succeeds](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) - -## Test and Deploy - -Use the built-in continuous integration in GitLab. - -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing(SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) - -*** - -# Editing this README - -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thank you to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. - -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. - -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. - -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. - -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. - -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. - -## Contributing -State if you are open to contributions and what your requirements are for accepting them. - -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. - -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. - -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. - -## License -For open source projects, say how it is licensed. - -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +# CyberArchWarehouse + +Main Repository for the Cyber-archaeology Warehouse Project + +## Description + +Beyond the buzzword “Metaverseâ€, technology is quickly approaching a convergence of virtual reality (VR), augmented reality (AR), digital twins, and globally accessible big data. In the field of archaeology, especially in excavations being carried out in the Holy Land, we have similarly pursued over the past twenty years development of technologies to digitally preserve, visualize, analyze and share digital twins of the archaeological record. Archaeology is an inherently destructive science necessitating all efforts to be made to adequately record data in the field and digitally store it in an as accurate and comprehensive record as possible. Once we have a digital twin of our data, digital immersive environments are naturally the most convenient means to fully take advantage of it for analysis and visualization. This projects end goal is to create an immersive Cyber-Archeaologists' Warehouse inside UE5 for analysis and study of digitized assets from field excavations and survey. We see this as the next step to digitization, analysis and collaboration that allows us to begin building out the metaverse of the archaeological past. + +Goals: +1. Any archaeological data format or application can be accessed in VR +2. Data can be studied as if sitting at a desk or computer +3. New interfaces to enhance study that go beyond desk or computer (Multiple displays, Touch tables, 3D floating artifacts) +4. Multi-user collaborative environment-many people can visit the warehouse at the same time +5. Cross-platform: VR/AR, PC, Web, Mac, Linux, Tablet + + +## Getting started + +To make it easy for you to get started with GitLab, here's a list of recommended next steps. + + +## Get the repository + +``` +cd C:\UnrealProjects +git clone https://gitlab.nrp-nautilus.io/ngsmith/cyberarchwarehouse.git +``` +## Add your files + +``` +cd C:\UnrealProjects\cyberarchwarehouse +git add [directory/file] +git status +``` + +## Commit and Push your files + +``` +git config --global http.postBuffer 2097152000 +git commit -am "new push" +git push origin main +``` + +## Upate your repository with Pull + +``` +git pull origin +``` + + +*** + +## Installation +TODO + +## Download External Project Assets + +In order to keep the repository small we have moved all large assets, materials and textures to the WorldBuilding folder. These assets will be packaged into the project but will not be stored on the repository. After cloning the repository, retreive the latest release zip file for WorldBuilding: https://gitlab.nrp-nautilus.io/ngsmith/cyberarchwarehouse/-/releases + +Unpack and store on your computer, preferably so that the folder structure is C:/UnrealProjects/WorldBuilding. +Now create a symbolic link to make it accessible from the content browser of UE5: +mklink /d C:\UnrealProjects\cyberarchwarehouse\Content\WorldBuilding C:\UnrealProjects\WorldBuilding + +Make sure to do this before opening the project for the first time. +Also do not add the WorldBuilding folder link to gitlab when commiting changes!!! + +## Connecting your Quest to same wifi as PC + +If at UCSD it is almost impossible to get onto the UCSD Protected and it blocks Airlink. +The easiest way to do get around this is to make your pc a Mobile Hotspot. Click in windows search bar for: "Mobile Hotspot" +Enable the hotspot and from the Quest find the wifi and enter the password + +You will now have a direct connection between your PC and Quest avoiding any network bottlenecks. + + +## Setting up OpenXR and Oculus Airlink + +1. Download and install Oculus app for windows: https://www.oculus.com/download_app/?id=1582076955407037 + +2. Select Airlink and then on your Quest go into Settings and enable Airlink + +3. If you are on the same wifi as your pc when you select Airlink on the quest it should already show your pc's name and the option to pair should be enabled. + +4. Pair your device and then Launch to get into the Oculus App running on your PC + +5. Go to Settings->Beta on the Oculus App and enable Public Test Channel and Developer Runtime Features + +6. Go to Settings->General and make sure OpenXR Runtime has Oculus set as active. + +7. You can now launch the Unreal Editor and you can select VR Preview + +## Troubleshooting Oculus PC App and Airlink connection + +If the 'pair' or 'launch' option is faded out this is the best solution: +1. Restart Quest +2. Open Task manager, go to Services Tab and make sure OVRService is set to stopped +3. Go to Details tab and end task OVRServiceLauncher if it is running. Also kill OVRService if running +4. Using File Explorer go to C:\Program Files\Oculus\Support\oculus-runtime and launch OVRServer_x64.exe +-This will restart Oculus App but it also opens a log window. +5. Make a desktop shortcut for OVRServer_x64.exe for future use. +6. Now if you need to restart Oculus app and get pair/launch active you just kill the OVRServer log window and restart it using the shortcut. + +## Mounting ArchData Folder and linking into the project + +Download and unzip rclone +https://rclone.org/downloads/ + +install winFSP (required for fuse mount, willf ail without this installed first) +https://github.com/winfsp/winfsp/releases/tag/v1.10 + +Open CMD, navigate to install folder + +run "rclone config", this will create the directory C:\Users\username\AppData\Roaming\rclone\ + +Hit q to quit + +Get rclone.cfg file (in repo) and add keyid and secretid + +copy config file (rclone.cfg) into C:\Users\username\AppData\Roaming\rclone\ or alternatively place same folder as rclone + +``` +rclone config file --to see config location +rclone config show --to see config data +``` + +run command to mount drive: +``` +rclone mount SeaweedS3:pvc-23f3cead-b240-4298-8c61-4e246dc1d8de P: --vfs-cache-mode full +``` +Now that you have the bucket mounted you can create a link to the project into the ArchData content folder [we will access all public shared archaeological data from here]: +``` +mklink /d C:\UnrealProjects\cyberarchwarehouse\Content\ArchData P:\ArchData +``` + +If you want to share large data privately use our cyberarch bucket, to mount: +rclone mount CephS3:cyberarch S: --vfs-cache-mode full + +## Syncing WorldBuilding Folder + +The WorldBuilding folder is stored on our SeaweedS3 bucket. +The best way to add it to your project is to access the most up to date one on SeaweedS3 +If you have followed mounting instructions above you can copy the WorldBuilding folder to your computer from the mount +If you want to sync latest updates use the following command with rclone: + +[Note this will remove any previous changes in your local WorldBuilding folder] +``` +rclone sync -i SeaweedS3:pvc-23f3cead-b240-4298-8c61-4e246dc1d8de/WorldBuilding C:/UnrealProjects/WorldBuilding +``` +After your local folder is synced mount it into the content browser folder: +``` +mklink /d C:\UnrealProjects\cyberarchwarehouse\Content\WorldBuilding C:/UnrealProjects/WorldBuilding +``` + +If you have made changes to your local WorldBuilding folder and want to add these to the shared use this command: + +[Note only run this command if you are sure you want to sync your folder because it will delete and change the synced destination folder] +``` +rclone sync -i C:/UnrealProjects/WorldBuilding SeaweedS3:pvc-23f3cead-b240-4298-8c61-4e246dc1d8de/WorldBuilding +``` +## Make CyberArchWarehouse Home for SteamVR + +If you have downloaded the Runtime of this project, you can make it your SteamVR Home! + +Edit C:\Program Files (x86)\Steam\steamapps\common\SteamVR\tools\tools.vrmanifest + +In "app_key": "openvr.tool.steamvr_environments" make following changes: + +``` +"binary_path_windows" : "C:/UnrealProjects/CyberArchWarehouseRelease/Windows/CyberArchWarehouse.exe", +"binary_path_osx" : "steamvr_environments/game/steamtours.sh", +"binary_path_linux" : "steamvr_environments/game/steamtours.sh", +"arguments": "-vr -RenderOffScreen", +``` +Save and Relaunch ALVR with SteamVR + +This will always boot you immediately into the project and return you to CyberArchWarehouse after running UE5 editor or other VR app. + +## Usage and Controls for UE5 project + +Movement: +Mouse: Rotates Yaw and Pitch View +Keyboard: WASD or [Up/Down/Left/Right] +Controller: Left Controller X/Y Axis for Moving + Right Controller X/Y Axis for rotating + +Jump: +Keyboard: Spacebar +Controller: Right Controller Trigger + +Toggle between 1st and 3rd perspective: +Keyboard: p or [1,3] +Controller: Right Controller B + +## Support +Contact ngsmith@ucsd.edu or join our Matrix channel: CyberArch Warehouse [https://matrix-to.nrp-nautilus.io/#/room/!GBRMuVTqUnhxjySkiS:matrix.nrp-nautilus.io?via=matrix.nrp-nautilus.io] + +## Roadmap +Stage 1: Interopability of base warehouse in Quest/Windows/Linux + +## Contributing +If you would like to contribute to this project contact ngsmith@ucsd.edu to be added. + +Only store blueprints and small datatypes inside the content folder (e.g. /cyberarchwarehouse/Content/). + +If you are importing or migrating assets that will be core to the project make sure to move them to WorldBuilding folder and place in the proper folder directory. Then zip the WorldBuilding folder and coordinate with other contributors to create a new release. + +All Archaeological Data should be outside the project stored on our SeaweedFS S3 to be dynamically loaded in. See above for mounting SeaweedFS to the project. + +If you are testing data create a temporary folder in the Content Browser and make sure not to add it to the repository. + +## Authors and acknowledgment + +Current Authors: +Neil G. Smith, +Haoyang Guo, +Sophia Boss, +Adrian Rodarte + +## License +TODO + +## Project status +Initial Development \ No newline at end of file diff --git a/Source/CyberProject.Target.cs b/Source/CyberProject.Target.cs new file mode 100644 index 0000000000000000000000000000000000000000..9afde594524c6bee632300fb966075cb317ea200 --- /dev/null +++ b/Source/CyberProject.Target.cs @@ -0,0 +1,15 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class CyberProjectTarget : TargetRules +{ + public CyberProjectTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Game; + DefaultBuildSettings = BuildSettingsVersion.V2; + + ExtraModuleNames.AddRange( new string[] { "CyberProject" } ); + } +} diff --git a/Source/CyberProject/CyberProject.Build.cs b/Source/CyberProject/CyberProject.Build.cs new file mode 100644 index 0000000000000000000000000000000000000000..6b25564460f77549fcfa22ffa3953411e2d0bd5f --- /dev/null +++ b/Source/CyberProject/CyberProject.Build.cs @@ -0,0 +1,23 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; + +public class CyberProject : ModuleRules +{ + public CyberProject(ReadOnlyTargetRules Target) : base(Target) + { + PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs; + + PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore" }); + + PrivateDependencyModuleNames.AddRange(new string[] { }); + + // Uncomment if you are using Slate UI + // PrivateDependencyModuleNames.AddRange(new string[] { "Slate", "SlateCore" }); + + // Uncomment if you are using online features + // PrivateDependencyModuleNames.Add("OnlineSubsystem"); + + // To include OnlineSubsystemSteam, add it to the plugins section in your uproject file with the Enabled attribute set to true + } +} diff --git a/Source/CyberProject/CyberProject.cpp b/Source/CyberProject/CyberProject.cpp new file mode 100644 index 0000000000000000000000000000000000000000..273ddeb0bb93a3b9b49891aaf6c70220ff61b50c --- /dev/null +++ b/Source/CyberProject/CyberProject.cpp @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#include "CyberProject.h" +#include "Modules/ModuleManager.h" + +IMPLEMENT_PRIMARY_GAME_MODULE( FDefaultGameModuleImpl, CyberProject, "CyberProject" ); diff --git a/Source/CyberProject/CyberProject.h b/Source/CyberProject/CyberProject.h new file mode 100644 index 0000000000000000000000000000000000000000..c56eab674f2d39a386e969ad1fc751dcb3d3dcc5 --- /dev/null +++ b/Source/CyberProject/CyberProject.h @@ -0,0 +1,6 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + diff --git a/Source/CyberProject/EmptyClass.cpp b/Source/CyberProject/EmptyClass.cpp new file mode 100644 index 0000000000000000000000000000000000000000..9fe1789519055ec7b25906018e2dec1e886a9a93 --- /dev/null +++ b/Source/CyberProject/EmptyClass.cpp @@ -0,0 +1,12 @@ +// Fill out your copyright notice in the Description page of Project Settings. + + +#include "EmptyClass.h" + +EmptyClass::EmptyClass() +{ +} + +EmptyClass::~EmptyClass() +{ +} diff --git a/Source/CyberProject/EmptyClass.h b/Source/CyberProject/EmptyClass.h new file mode 100644 index 0000000000000000000000000000000000000000..877fd28fc639406491cb87a481dd5760b6471547 --- /dev/null +++ b/Source/CyberProject/EmptyClass.h @@ -0,0 +1,15 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +#pragma once + +#include "CoreMinimal.h" + +/** + * + */ +class CYBERPROJECT_API EmptyClass +{ +public: + EmptyClass(); + ~EmptyClass(); +}; diff --git a/Source/CyberProjectEditor.Target.cs b/Source/CyberProjectEditor.Target.cs new file mode 100644 index 0000000000000000000000000000000000000000..dc9ed5b5d3addad62b6bc9e98c497994f08fdbf1 --- /dev/null +++ b/Source/CyberProjectEditor.Target.cs @@ -0,0 +1,15 @@ +// Fill out your copyright notice in the Description page of Project Settings. + +using UnrealBuildTool; +using System.Collections.Generic; + +public class CyberProjectEditorTarget : TargetRules +{ + public CyberProjectEditorTarget(TargetInfo Target) : base(Target) + { + Type = TargetType.Editor; + DefaultBuildSettings = BuildSettingsVersion.V2; + + ExtraModuleNames.AddRange( new string[] { "CyberProject" } ); + } +}